diff --git a/.changeset/calm-bulldogs-bake.md b/.changeset/calm-bulldogs-bake.md new file mode 100644 index 000000000000..f90e37a250c2 --- /dev/null +++ b/.changeset/calm-bulldogs-bake.md @@ -0,0 +1,5 @@ +--- +"create-cloudflare": minor +--- + +Create a custom not-found edge route for Next.js applications using the app router diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 7104887fe51d..d5820286ff58 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -112,7 +112,7 @@ export const stripAnsi = (str: string) => { return str.replace(regex, ""); }; -export const crash = (msg?: string): never => { +export const crash: (msg?: string) => never = (msg) => { if (msg) { process.stderr.write(red(msg)); process.stderr.write("\n"); diff --git a/packages/create-cloudflare/src/cli.ts b/packages/create-cloudflare/src/cli.ts index 597112a06464..7fd7ca55ab0b 100644 --- a/packages/create-cloudflare/src/cli.ts +++ b/packages/create-cloudflare/src/cli.ts @@ -105,11 +105,11 @@ export const runCli = async (args: Partial) => { }); if (!type) { - return crash("An application type must be specified to continue."); + crash("An application type must be specified to continue."); } if (!Object.keys(templateMap).includes(type)) { - return crash(`Unknown application type provided: ${type}.`); + crash(`Unknown application type provided: ${type}.`); } const validatedArgs: C3Args = { diff --git a/packages/create-cloudflare/src/frameworks/index.ts b/packages/create-cloudflare/src/frameworks/index.ts index f8d970015a1d..9d8156de3041 100644 --- a/packages/create-cloudflare/src/frameworks/index.ts +++ b/packages/create-cloudflare/src/frameworks/index.ts @@ -40,7 +40,7 @@ export const getFrameworkCli = ( withVersion = true ) => { if (!ctx.framework) { - return crash("Framework not specified."); + crash("Framework not specified."); } const framework = ctx.framework diff --git a/packages/create-cloudflare/src/frameworks/next/index.ts b/packages/create-cloudflare/src/frameworks/next/index.ts index bd20775a36c1..88be6761523b 100644 --- a/packages/create-cloudflare/src/frameworks/next/index.ts +++ b/packages/create-cloudflare/src/frameworks/next/index.ts @@ -1,5 +1,5 @@ -import { mkdirSync } from "fs"; -import { updateStatus, warn } from "@cloudflare/cli"; +import { existsSync, mkdirSync } from "fs"; +import { crash, updateStatus, warn } from "@cloudflare/cli"; import { brandColor, dim } from "@cloudflare/cli/colors"; import { processArgument } from "helpers/args"; import { installPackages, runFrameworkGenerator } from "helpers/command"; @@ -18,6 +18,8 @@ import { apiAppDirHelloTs, apiPagesDirHelloJs, apiPagesDirHelloTs, + appDirNotFoundJs, + appDirNotFoundTs, } from "./templates"; import type { C3Args, FrameworkConfig, PagesGeneratorContext } from "types"; @@ -53,26 +55,41 @@ const getApiTemplate = ( const configure = async (ctx: PagesGeneratorContext) => { const projectName = ctx.project.name; - // Add a compatible function handler example - const path = probePaths( - [ - `${projectName}/pages/api`, - `${projectName}/src/pages/api`, - `${projectName}/src/app/api`, - `${projectName}/app/api`, - `${projectName}/src/app`, - `${projectName}/app`, - ], - "Could not find the `/api` or `/app` directory" - ); + const path = probePaths([ + `${projectName}/pages/api`, + `${projectName}/src/pages/api`, + `${projectName}/src/app/api`, + `${projectName}/app/api`, + `${projectName}/src/app`, + `${projectName}/app`, + ]); + + if (!path) { + crash("Could not find the `/api` or `/app` directory"); + } // App directory template may not generate an API route handler, so we update the path to add an `api` directory. const apiPath = path.replace(/\/app$/, "/app/api"); - const [handlerPath, handlerFile] = getApiTemplate( - apiPath, - usesTypescript(projectName) - ); + const usesTs = usesTypescript(projectName); + + const appDirPath = probePaths([ + `${projectName}/src/app`, + `${projectName}/app`, + ]); + if (appDirPath) { + // Add a custom app not-found edge route as recommended in next-on-pages + // (see: https://github.com/cloudflare/next-on-pages/blob/2b5c8f25/packages/next-on-pages/docs/gotchas.md#not-found) + const notFoundPath = `${appDirPath}/not-found.${usesTs ? "tsx" : "js"}`; + if (!existsSync(notFoundPath)) { + const notFoundContent = usesTs ? appDirNotFoundTs : appDirNotFoundJs; + writeFile(notFoundPath, notFoundContent); + updateStatus("Created a custom edge not-found route"); + } + } + + // Add a compatible function handler example + const [handlerPath, handlerFile] = getApiTemplate(apiPath, usesTs); writeFile(handlerPath, handlerFile); updateStatus("Created an example API route handler"); diff --git a/packages/create-cloudflare/src/frameworks/next/templates.ts b/packages/create-cloudflare/src/frameworks/next/templates.ts index 372cc9ff0531..665eccb7bd5e 100644 --- a/packages/create-cloudflare/src/frameworks/next/templates.ts +++ b/packages/create-cloudflare/src/frameworks/next/templates.ts @@ -45,3 +45,127 @@ export async function GET(request) { return new Response(JSON.stringify({ name: 'John Doe' })) } `; + +// Simplified and adjusted version of the Next.js built-in not-found component (https://github.com/vercel/next.js/blob/1c65c5575/packages/next/src/client/components/not-found-error.tsx) +export const appDirNotFoundJs = ` +export const runtime = "edge"; + +export default function NotFound() { + return ( + <> + 404: This page could not be found. +
+
+