├── .changeset ├── README.md └── config.json ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ └── feature-request.yml ├── actions │ ├── cache-turbo │ │ └── action.yml │ └── node-setup │ │ └── action.yaml ├── version-script.js └── workflows │ ├── beta-release.yml │ ├── configs-docs-scraping.yml │ ├── create-pullrequest-prerelease.yml │ ├── pull-request.yml │ ├── release.yml │ ├── semgrep.yml │ └── write-prerelease-comment.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── docs ├── contributing.md └── technical │ ├── README.md │ ├── lazy-loading.md │ └── routing.md ├── internal-packages ├── docs-scraper │ ├── .eslintrc.js │ ├── package.json │ ├── scripts │ │ ├── checkDocs.ts │ │ ├── createOrUpdateIssue.ts │ │ ├── generateConfigsTable.ts │ │ └── utils │ │ │ ├── NextConfig.ts │ │ │ ├── fromGithubAction.ts │ │ │ ├── index.ts │ │ │ └── scrapeConfigs.ts │ └── tsconfig.json ├── eslint-config-next-on-pages │ ├── index.js │ └── package.json ├── next-dev │ ├── .eslintrc.js │ ├── README.md │ ├── package.json │ ├── src │ │ ├── deprecated.ts │ │ ├── index.ts │ │ └── shared.ts │ └── tsconfig.json └── next-on-pages-tsconfig │ ├── package.json │ └── tsconfig.json ├── package-lock.json ├── package.json ├── packages ├── eslint-plugin-next-on-pages │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── docs │ │ └── rules │ │ │ ├── no-app-nodejs-dynamic-ssg.md │ │ │ ├── no-nodejs-runtime.md │ │ │ ├── no-pages-nodejs-dynamic-ssg.md │ │ │ └── no-unsupported-configs.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── rules │ │ │ ├── no-app-nodejs-dynamic-ssg.ts │ │ │ ├── no-nodejs-runtime.ts │ │ │ ├── no-pages-nodejs-dynamic-ssg.ts │ │ │ └── no-unsupported-configs.ts │ │ └── utils │ │ │ ├── ast-traversal.ts │ │ │ └── extract-paths.ts │ ├── tests │ │ ├── rules │ │ │ ├── no-app-nodejs-dynamic-ssg.test.ts │ │ │ ├── no-nodejs-runtime.test.ts │ │ │ ├── no-pages-nodejs-dynamic-ssg.test.ts │ │ │ └── no-unsupported-configs.test.ts │ │ └── utils │ │ │ ├── ast-traversal.test.ts │ │ │ └── extract-paths.test.ts │ ├── tsconfig.json │ └── vitest.config.ts └── next-on-pages │ ├── .eslintrc.js │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ └── index.js │ ├── build-metadata.d.ts │ ├── build-no-nodejs-compat-flag-static-error-page.mjs │ ├── docs │ ├── advanced-usage.md │ ├── caching.md │ ├── examples.md │ ├── static-assets-routing.md │ └── supported.md │ ├── env.d.ts │ ├── no-nodejs-compat-flag-static-error-page │ └── assets │ │ ├── img │ │ ├── input.png │ │ └── result.png │ │ └── index.html │ ├── package.json │ ├── src │ ├── api │ │ ├── getRequestContext.ts │ │ └── index.ts │ ├── buildApplication │ │ ├── buildApplication.ts │ │ ├── buildMetadataFiles.ts │ │ ├── buildSummary.ts │ │ ├── buildVercelOutput.ts │ │ ├── buildWorkerFile.ts │ │ ├── generateGlobalJs.ts │ │ ├── getVercelConfig.ts │ │ ├── index.ts │ │ ├── processUtils.ts │ │ ├── processVercelFunctions │ │ │ ├── ast.ts │ │ │ ├── build.ts │ │ │ ├── configs.ts │ │ │ ├── dedupeEdgeFunctions.ts │ │ │ ├── edgeFunctions.ts │ │ │ ├── index.ts │ │ │ ├── invalidFunctions.ts │ │ │ └── prerenderFunctions.ts │ │ └── processVercelOutput.ts │ ├── cli.ts │ ├── fetch-handler │ │ └── index.ts │ ├── index.ts │ └── utils │ │ ├── fs.ts │ │ ├── getNodeEnv.ts │ │ ├── index.ts │ │ ├── os.ts │ │ ├── packages.ts │ │ ├── requestContext.ts │ │ ├── routing.ts │ │ ├── str.ts │ │ └── version.ts │ ├── templates │ ├── _worker.js │ │ ├── handleRequest.ts │ │ ├── index.ts │ │ ├── routes-matcher.ts │ │ ├── routesIsolation.ts │ │ └── utils │ │ │ ├── cache.ts │ │ │ ├── doImport.ts │ │ │ ├── fetch.ts │ │ │ ├── http.ts │ │ │ ├── images.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── pcre.ts │ │ │ ├── request.ts │ │ │ └── routing.ts │ └── cache │ │ ├── adaptor.ts │ │ ├── cache-api.ts │ │ ├── index.ts │ │ └── kv.ts │ ├── tests │ ├── _helpers │ │ └── index.ts │ ├── src │ │ ├── api │ │ │ └── getRequestContext.test.ts │ │ ├── buildApplication │ │ │ ├── buildSummary.test.ts │ │ │ ├── generateGlobalJs.test.ts │ │ │ ├── getVercelConfig.test.ts │ │ │ ├── processVercelFunctions │ │ │ │ ├── edgeFunctions.test.ts │ │ │ │ ├── invalidFunctions.test.ts │ │ │ │ └── prerenderFunctions.test.ts │ │ │ └── processVercelOutput.test.ts │ │ └── utils │ │ │ ├── fs.test.ts │ │ │ ├── routing.test.ts │ │ │ └── str.test.ts │ └── templates │ │ └── utils │ │ ├── getNodeEnv.test.ts │ │ ├── http.test.ts │ │ ├── images.test.ts │ │ ├── matcher.test.ts │ │ └── pcre.test.ts │ ├── tsconfig.api.json │ ├── tsconfig.fetch-handler.json │ ├── tsconfig.json │ ├── vercel.types.d.ts │ └── vitest.config.ts ├── pages-e2e ├── .env.example ├── features │ ├── _utils │ │ ├── copyWorkspaceAssets.ts │ │ ├── getAssertVisible.ts │ │ └── runWithHardNavigationsChecking.ts │ ├── appConfigsRewritesRedirectsHeaders │ │ ├── assets │ │ │ └── app │ │ │ │ ├── api │ │ │ │ └── configs-headers │ │ │ │ │ ├── not-to-apply │ │ │ │ │ └── some-route │ │ │ │ │ │ └── route.js │ │ │ │ │ └── to-apply │ │ │ │ │ └── some-route │ │ │ │ │ └── route.js │ │ │ │ └── configs-rewrites │ │ │ │ ├── dynamic │ │ │ │ └── [page] │ │ │ │ │ └── page.jsx │ │ │ │ ├── header-somewhere-else │ │ │ │ └── page.jsx │ │ │ │ ├── query-somewhere-else │ │ │ │ └── page.jsx │ │ │ │ ├── rewritten-wildcard │ │ │ │ └── my-page │ │ │ │ │ └── page.jsx │ │ │ │ ├── some-page │ │ │ │ └── page.jsx │ │ │ │ └── wildcard │ │ │ │ └── my-page │ │ │ │ └── page.jsx │ │ ├── headers.test.ts │ │ ├── headers.ts │ │ ├── main.feature │ │ ├── redirects.test.ts │ │ ├── redirects.ts │ │ ├── rewrites.test.ts │ │ ├── rewrites.ts │ │ └── setup.ts │ ├── appConfigsTrailingSlashFalse │ │ ├── assets │ │ │ └── app │ │ │ │ └── api │ │ │ │ └── routing-trailing-slash-test │ │ │ │ └── route.js │ │ ├── main.feature │ │ ├── setup.ts │ │ └── trailingSlash.test.ts │ ├── appConfigsTrailingSlashTrue │ │ ├── assets │ │ │ └── app │ │ │ │ └── api │ │ │ │ └── routing-trailing-slash-test │ │ │ │ └── route.js │ │ ├── main.feature │ │ ├── setup.ts │ │ └── trailingSlash.test.ts │ ├── appGetRequestContext │ │ ├── assets │ │ │ ├── app │ │ │ │ ├── api │ │ │ │ │ └── get-request-context │ │ │ │ │ │ └── route.js │ │ │ │ └── get-request-context │ │ │ │ │ └── page.jsx │ │ │ └── wrangler.toml │ │ ├── get-request-context.test.ts │ │ ├── main.feature │ │ └── setup.ts │ ├── appMiddleware │ │ ├── assets │ │ │ ├── app │ │ │ │ ├── api │ │ │ │ │ └── middleware-test │ │ │ │ │ │ ├── hello │ │ │ │ │ │ └── route.js │ │ │ │ │ │ └── unreachable │ │ │ │ │ │ └── route.js │ │ │ │ └── middleware-test │ │ │ │ │ ├── pageA │ │ │ │ │ └── page.jsx │ │ │ │ │ ├── pageB │ │ │ │ │ └── page.jsx │ │ │ │ │ └── unreachable │ │ │ │ │ └── page.jsx │ │ │ └── middleware.js │ │ ├── main.feature │ │ ├── middleware.test.ts │ │ └── setup.ts │ ├── appNoNodeJsCompatFlag │ │ ├── main.feature │ │ ├── no-nodejs_compat-flag.test.ts │ │ └── setup.ts │ ├── appRouting │ │ ├── 404.test.ts │ │ ├── 500.test.ts │ │ ├── assets │ │ │ └── app │ │ │ │ ├── 500-error │ │ │ │ └── page.jsx │ │ │ │ ├── ssg-dynamic │ │ │ │ └── [slug] │ │ │ │ │ └── page.jsx │ │ │ │ └── ssr-dynamic │ │ │ │ ├── catch-all │ │ │ │ └── [...pets] │ │ │ │ │ └── page.js │ │ │ │ ├── optional-catch-all │ │ │ │ └── [[...colors]] │ │ │ │ │ └── page.jsx │ │ │ │ └── page │ │ │ │ └── [pageName] │ │ │ │ └── page.jsx │ │ ├── main.feature │ │ ├── setup.ts │ │ ├── ssg-dynamic.test.ts │ │ └── ssr-dynamic.test.ts │ ├── appRoutingSsrDynamicCatchAll │ │ ├── assets │ │ │ └── app │ │ │ │ └── ssr-dynamic-catch-all │ │ │ │ ├── catch-all │ │ │ │ └── [...pets] │ │ │ │ │ └── page.js │ │ │ │ └── optional-catch-all │ │ │ │ └── [[...colors]] │ │ │ │ └── page.jsx │ │ ├── main.feature │ │ ├── setup.ts │ │ └── ssr-dynamic-catch-all.test.ts │ ├── appServerActions │ │ ├── assets │ │ │ └── app │ │ │ │ └── server-actions │ │ │ │ └── simple-kv-form │ │ │ │ └── page.jsx │ │ ├── main.feature │ │ ├── server-actions.test.ts │ │ └── setup.ts │ ├── appStaticNotFoundWithEdgeLayout │ │ ├── app.test.ts │ │ ├── main.feature │ │ └── setup.ts │ ├── appWasm │ │ ├── assets │ │ │ └── app │ │ │ │ └── wasm │ │ │ │ └── add-one │ │ │ │ ├── add-one.wasm │ │ │ │ └── page.jsx │ │ ├── main.feature │ │ ├── setup.ts │ │ └── wasm.test.ts │ ├── customEntrypoint │ │ ├── assets │ │ │ └── custom-entrypoint.ts │ │ ├── custom-entrypoint.test.ts │ │ ├── main.feature │ │ └── setup.ts │ ├── issue593 │ │ ├── issue.test.ts │ │ ├── main.feature │ │ └── setup.ts │ ├── issue696 │ │ ├── issue.test.ts │ │ ├── main.feature │ │ └── setup.ts │ ├── issue797 │ │ ├── issue.test.ts │ │ ├── main.feature │ │ └── setup.ts │ ├── pagesConfigsI18n │ │ ├── main.feature │ │ ├── next.config.i18n.test.ts │ │ └── setup.ts │ ├── pagesConfigsRewritesRedirectsHeaders │ │ ├── assets │ │ │ └── pages │ │ │ │ ├── api │ │ │ │ └── configs-headers │ │ │ │ │ ├── not-to-apply │ │ │ │ │ └── some-route.js │ │ │ │ │ └── to-apply │ │ │ │ │ └── some-route.js │ │ │ │ └── configs-rewrites │ │ │ │ ├── dynamic │ │ │ │ └── [page].jsx │ │ │ │ ├── header-somewhere-else.jsx │ │ │ │ ├── query-somewhere-else.jsx │ │ │ │ ├── rewritten-wildcard │ │ │ │ └── my-page.jsx │ │ │ │ ├── some-page.jsx │ │ │ │ └── wildcard │ │ │ │ └── my-page.jsx │ │ ├── headers.test.ts │ │ ├── headers.ts │ │ ├── main.feature │ │ ├── redirects.test.ts │ │ ├── redirects.ts │ │ ├── rewrites.test.ts │ │ ├── rewrites.ts │ │ └── setup.ts │ ├── pagesGetRequestContext │ │ ├── assets │ │ │ ├── pages │ │ │ │ └── api │ │ │ │ │ └── get-request-context.js │ │ │ └── wrangler.toml │ │ ├── get-request-context.test.ts │ │ ├── main.feature │ │ └── setup.ts │ ├── pagesIssue578 │ │ ├── issue.test.ts │ │ ├── main.feature │ │ └── setup.ts │ ├── pagesMiddleware │ │ ├── assets │ │ │ ├── middleware.js │ │ │ └── pages │ │ │ │ ├── api │ │ │ │ └── middleware-test │ │ │ │ │ ├── hello.js │ │ │ │ │ └── unreachable.js │ │ │ │ └── middleware-test │ │ │ │ ├── pageA.jsx │ │ │ │ └── pageB.jsx │ │ ├── main.feature │ │ ├── middleware.test.ts │ │ └── setup.ts │ ├── pagesRouting │ │ ├── 404.test.ts │ │ ├── 500.test.ts │ │ ├── assets │ │ │ └── pages │ │ │ │ ├── 500-error.jsx │ │ │ │ ├── api │ │ │ │ └── routing-trailing-slash-test.js │ │ │ │ ├── ssg-dynamic │ │ │ │ └── [slug].jsx │ │ │ │ └── ssr-dynamic │ │ │ │ ├── catch-all │ │ │ │ └── [...pets].jsx │ │ │ │ ├── optional-catch-all │ │ │ │ └── [[...colors]].jsx │ │ │ │ └── page │ │ │ │ └── [pageName].tsx │ │ ├── main.feature │ │ ├── setup.ts │ │ ├── ssg-dynamic.test.ts │ │ ├── ssr-dynamic.test.ts │ │ └── trailingSlash.test.ts │ ├── pagesRoutingSsrDynamicCatchAll │ │ ├── assets │ │ │ └── pages │ │ │ │ └── ssr-dynamic-catch-all │ │ │ │ ├── catch-all │ │ │ │ └── [...pets].jsx │ │ │ │ └── optional-catch-all │ │ │ │ └── [[...colors]].jsx │ │ ├── main.feature │ │ ├── setup.ts │ │ └── ssr-dynamic-catch-all.test.ts │ ├── pagesWasm │ │ ├── assets │ │ │ └── pages │ │ │ │ └── wasm │ │ │ │ └── add-one │ │ │ │ ├── add-one.wasm │ │ │ │ └── index.jsx │ │ ├── main.feature │ │ ├── setup.ts │ │ └── wasm.test.ts │ ├── setup-types.d.ts │ ├── simpleAppApiRoutes │ │ ├── api-routes.test.ts │ │ ├── assets │ │ │ └── app │ │ │ │ └── api │ │ │ │ ├── headers │ │ │ │ └── route.js │ │ │ │ └── hello │ │ │ │ └── route.js │ │ ├── main.feature │ │ └── setup.ts │ ├── simpleAppSsrRoutes │ │ ├── assets │ │ │ └── app │ │ │ │ ├── routeA │ │ │ │ └── page.jsx │ │ │ │ └── ssr-navigation │ │ │ │ ├── layout.js │ │ │ │ ├── page.js │ │ │ │ ├── pageA │ │ │ │ └── page.js │ │ │ │ └── pageB │ │ │ │ └── page.js │ │ ├── main.feature │ │ ├── navigation.test.ts │ │ ├── setup.ts │ │ └── ssr-routes.test.ts │ ├── simpleAppStaticRoutesAndAssets │ │ ├── assets.test.ts │ │ ├── assets │ │ │ └── app │ │ │ │ ├── navigation │ │ │ │ ├── layout.js │ │ │ │ ├── page.js │ │ │ │ ├── pageA │ │ │ │ │ └── page.js │ │ │ │ └── pageB │ │ │ │ │ └── page.js │ │ │ │ └── staticRouteA │ │ │ │ └── page.jsx │ │ ├── main.feature │ │ ├── navigation.test.ts │ │ ├── setup.ts │ │ └── static-routes.test.ts │ ├── simplePagesApiRoutes │ │ ├── api-routes.test.ts │ │ ├── assets │ │ │ └── pages │ │ │ │ └── api │ │ │ │ ├── headers.js │ │ │ │ └── hello.js │ │ ├── main.feature │ │ └── setup.ts │ ├── simplePagesSsrRoutes │ │ ├── assets │ │ │ └── pages │ │ │ │ ├── routeA.jsx │ │ │ │ └── ssr-navigation │ │ │ │ ├── index.js │ │ │ │ ├── layout.js │ │ │ │ ├── pageA.js │ │ │ │ └── pageB.js │ │ ├── main.feature │ │ ├── navigation.test.ts │ │ ├── setup.ts │ │ └── ssr-routes.test.ts │ └── simplePagesStaticRoutesAndAssets │ │ ├── assets.test.ts │ │ ├── assets │ │ └── pages │ │ │ ├── navigation │ │ │ ├── index.js │ │ │ ├── layout.js │ │ │ ├── pageA.js │ │ │ └── pageB.js │ │ │ └── staticRouteA.jsx │ │ ├── main.feature │ │ ├── navigation.test.ts │ │ ├── setup.ts │ │ └── static-routes.test.ts ├── fixtures │ ├── app13.4.0 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── app │ │ │ ├── favicon.ico │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── page.module.css │ │ │ └── page.tsx │ │ ├── main.fixture │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── next.svg │ │ │ └── vercel.svg │ │ └── tsconfig.json │ ├── app14.0.0 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── app │ │ │ ├── favicon.ico │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── page.module.css │ │ │ └── page.tsx │ │ ├── main.fixture │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── next.svg │ │ │ └── vercel.svg │ │ └── tsconfig.json │ ├── appLatest │ │ ├── main.fixture │ │ └── setup.sh │ ├── appLatestNoNodeJsCompat │ │ ├── main.fixture │ │ └── setup.sh │ ├── appStaticNotFoundWithEdgeLayout │ │ ├── .gitignore │ │ ├── README.md │ │ ├── app │ │ │ ├── layout.tsx │ │ │ ├── not-found.tsx │ │ │ └── page.tsx │ │ ├── main.fixture │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── next.svg │ │ │ └── vercel.svg │ │ ├── setup.sh │ │ └── tsconfig.json │ ├── issue593App │ │ ├── .gitignore │ │ ├── README.md │ │ ├── main.fixture │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ └── app │ │ │ │ ├── [...slug] │ │ │ │ └── page.tsx │ │ │ │ ├── api │ │ │ │ └── hello │ │ │ │ │ └── route.ts │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ └── tsconfig.json │ ├── issue593Pages │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── README.md │ │ ├── main.fixture │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ └── pages │ │ │ │ ├── [...slug].tsx │ │ │ │ ├── _app.tsx │ │ │ │ ├── _document.tsx │ │ │ │ ├── api │ │ │ │ └── hello.ts │ │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── issue696App │ │ ├── .gitignore │ │ ├── README.md │ │ ├── app │ │ │ ├── api │ │ │ │ └── hello │ │ │ │ │ └── route.ts │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── main.fixture │ │ ├── middleware.ts │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ └── tsconfig.json │ ├── issue696Pages │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── README.md │ │ ├── main.fixture │ │ ├── middleware.ts │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ ├── api │ │ │ │ └── hello.ts │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── issue797app │ │ ├── .gitignore │ │ ├── env.d.ts │ │ ├── main.fixture │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ ├── app │ │ │ │ ├── layout.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ │ └── middleware.ts │ │ ├── tsconfig.json │ │ └── wrangler.toml │ ├── pages12.3.0 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── main.fixture │ │ ├── next-env.d.ts │ │ ├── next.config.mjs │ │ ├── package.json │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ ├── api │ │ │ │ └── version.js │ │ │ └── index.tsx │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── vercel.svg │ │ ├── styles │ │ │ ├── Home.module.css │ │ │ └── globals.css │ │ └── tsconfig.json │ ├── pages13.0.0 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── main.fixture │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ └── index.tsx │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── vercel.svg │ │ ├── styles │ │ │ ├── Home.module.css │ │ │ └── globals.css │ │ └── tsconfig.json │ ├── pages14.0.0_i18n │ │ ├── .gitignore │ │ ├── README.md │ │ ├── components │ │ │ ├── links.tsx │ │ │ ├── locale-info.tsx │ │ │ └── locale-switcher.tsx │ │ ├── main.fixture │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── pages │ │ │ ├── get-server-side-props.tsx │ │ │ ├── get-static-props │ │ │ │ ├── [slug].tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── pagesIssue578 │ │ ├── .gitignore │ │ ├── README.md │ │ ├── main.fixture │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── pages │ │ │ ├── [[...path]].tsx │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ └── api │ │ │ │ └── hello.ts │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── next.svg │ │ │ └── vercel.svg │ │ ├── setup.sh │ │ ├── styles │ │ │ ├── Home.module.css │ │ │ └── globals.css │ │ └── tsconfig.json │ └── pagesLatest │ │ ├── main.fixture │ │ └── setup.sh ├── package.json ├── test-types.d.ts ├── tsconfig-base.json ├── tsconfig.json └── vitest.config.ts └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [["@cloudflare/next-on-pages", "eslint-plugin-next-on-pages"]], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: ⚡ Feature Request 2 | description: Request a new feature 3 | title: '[⚡ Feature]: ' 4 | labels: ['enhancement'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you very much for taking the time to fill out this feature request! 10 | 11 | Before creating the issue please make sure that: 12 | 13 | - There isn't already an existing issue covering the feature request, if there is, please add a comment there instead. 14 | 15 | - What you are suggesting is a new feature and not one that should be present but is missing, in that case open an bug report instead. 16 | 17 | - type: textarea 18 | id: description 19 | attributes: 20 | label: Description 21 | description: Please provide a detailed description of the new feature. 22 | validations: 23 | required: true 24 | 25 | - type: textarea 26 | id: additional-info 27 | attributes: 28 | label: Additional Information 29 | description: Is there anything else you'd like to add? 30 | validations: 31 | required: false 32 | 33 | - type: checkboxes 34 | id: help 35 | attributes: 36 | label: Would you like to help? 37 | options: 38 | - label: Would you like to help implement this feature? 39 | -------------------------------------------------------------------------------- /.github/actions/cache-turbo/action.yml: -------------------------------------------------------------------------------- 1 | name: Cache turbo 2 | description: Attempts to cache turborepo files for the current workflow and job 3 | inputs: 4 | script: 5 | description: target script for the turborepo cache 6 | required: true 7 | runs: 8 | using: composite 9 | steps: 10 | - name: Cache turbo 11 | id: turbo-cache 12 | uses: actions/cache@v3 13 | with: 14 | path: ./turbo-cache 15 | key: cache-turbo-${{ github.workflow }}-${{ github.job }}-${{ inputs.script }}-${{ github.base_ref }}-${{ github.ref_name }}-${{ github.sha }} 16 | restore-keys: | 17 | cache-turbo-${{ github.workflow }}-${{ github.job }}-${{ inputs.script }}-${{ github.base_ref }}-${{ github.ref_name }}-${{ github.sha }} 18 | cache-turbo-${{ github.workflow }}-${{ github.job }}-${{ inputs.script }}-${{ github.base_ref }}-${{ github.ref_name }} 19 | cache-turbo-${{ github.workflow }}-${{ github.job }}-${{ inputs.script }}-${{ github.base_ref }} 20 | cache-turbo-${{ github.workflow }}-${{ github.job }}-${{ inputs.script }} 21 | cache-turbo-${{ github.workflow }}-${{ github.job }} 22 | -------------------------------------------------------------------------------- /.github/workflows/beta-release.yml: -------------------------------------------------------------------------------- 1 | name: Beta Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | if: ${{ github.repository_owner == 'cloudflare' }} 13 | name: Release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup Node 20 | uses: ./.github/actions/node-setup 21 | 22 | - name: Modify package.json version 23 | run: node .github/version-script.js BETA 24 | 25 | - name: Build 26 | run: npm run build 27 | 28 | - name: Publish next-on-pages Beta to NPM 29 | working-directory: ./packages/next-on-pages 30 | run: npm publish --tag beta 31 | env: 32 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | 34 | - name: Publish eslint-plugin-next-on-pages Beta to NPM 35 | working-directory: ./packages/eslint-plugin-next-on-pages 36 | run: npm publish --tag beta 37 | env: 38 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 39 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | if: ${{ github.repository_owner == 'cloudflare' }} 13 | name: Release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup Node 20 | uses: ./.github/actions/node-setup 21 | 22 | - name: Create Release Pull Request or Publish to npm 23 | id: changesets 24 | uses: changesets/action@v1 25 | with: 26 | publish: npm run publish 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: {} 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - main 7 | - master 8 | schedule: 9 | - cron: '0 0 * * *' 10 | name: Semgrep config 11 | jobs: 12 | semgrep: 13 | name: semgrep/ci 14 | runs-on: ubuntu-20.04 15 | env: 16 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 17 | SEMGREP_URL: https://cloudflare.semgrep.dev 18 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 20 | container: 21 | image: returntocorp/semgrep 22 | steps: 23 | - uses: actions/checkout@v3 24 | - run: semgrep ci 25 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.10.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/CHANGELOG.md 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | "semi": true, 6 | "trailingComma": "all" 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "json.schemas": [ 3 | { 4 | "fileMatch": ["main.fixture"], 5 | "url": "./node_modules/@cfpreview/pages-e2e-test-runner-cli/schemas/fixture-schema.jsonc" 6 | }, 7 | { 8 | "fileMatch": ["main.feature"], 9 | "url": "./node_modules/@cfpreview/pages-e2e-test-runner-cli/schemas/fixture-schema.jsonc" 10 | } 11 | ], 12 | "files.associations": { 13 | "main.fixture": "jsonc", 14 | "main.feature": "jsonc" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/technical/README.md: -------------------------------------------------------------------------------- 1 | # Technical Documentation 2 | 3 | - [Lazy Loading](./lazy-loading.md) 4 | 5 | Documentation explaining the lazy loading strategy used by `@cloudflare/next-on-pages` and how it allows loading and evaluating only the JavaScript code relevant to each request. 6 | 7 | - [Routing](./routing.md) 8 | 9 | An in-depth look at how the (runtime) routing system implemented in `@cloudflare/next-on-pages` works, and how it matches and serves all incoming requests. It goes over the inner workings of processing a request and explains the design decisions behind each step. 10 | -------------------------------------------------------------------------------- /internal-packages/docs-scraper/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@cloudflare/eslint-config-next-on-pages'], 4 | rules: { 5 | 'no-console': 'off', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /internal-packages/docs-scraper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cloudflare/next-on-pages-docs-scraper", 3 | "private": true, 4 | "scripts": { 5 | "lint": "eslint scripts", 6 | "types-check": "tsc --noEmit", 7 | "create-or-update-issue": "ts-node scripts/createOrUpdateIssue.ts", 8 | "check-docs": "ts-node scripts/checkDocs.ts", 9 | "generate-configs-table": "ts-node scripts/generateConfigsTable.ts" 10 | }, 11 | "dependencies": { 12 | "@actions/core": "^1.10.0", 13 | "@actions/github": "^5.1.1", 14 | "puppeteer": "^20.7.4", 15 | "ts-node": "^10.9.1", 16 | "typescript": "^5.1.6" 17 | }, 18 | "devDependencies": { 19 | "eslint": "^8.35.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internal-packages/docs-scraper/scripts/generateConfigsTable.ts: -------------------------------------------------------------------------------- 1 | import { fromGithubAction, scrapeConfigs } from './utils'; 2 | import { setOutput } from '@actions/core'; 3 | 4 | void (async function (): Promise { 5 | const { allNextConfigs } = await scrapeConfigs(false); 6 | 7 | const tableHeadings = '| Option | Next Docs | Support |'; 8 | const divisor = '| ------ | ---------- | ------- |'; 9 | 10 | const tableLines = [tableHeadings, divisor]; 11 | 12 | allNextConfigs.forEach(config => { 13 | const option = `${config.configName}`; 14 | const pagesLink = config.urls.find(url => url.type === 'pages'); 15 | const appLink = config.urls.find(url => url.type === 'app'); 16 | const nextDocsLinks = []; 17 | if (pagesLink) { 18 | nextDocsLinks.push(`[pages](${pagesLink.href})`); 19 | } 20 | if (appLink) { 21 | nextDocsLinks.push(`[app](${appLink.href})`); 22 | } 23 | const nextDocs = nextDocsLinks.join(', '); 24 | const support = '???'; 25 | tableLines.push(`| ${option} | ${nextDocs} | ${support} |`); 26 | }); 27 | 28 | const table = tableLines.join('\n'); 29 | 30 | console.log(table); 31 | 32 | if (fromGithubAction()) { 33 | setOutput('table', table); 34 | } 35 | })(); 36 | -------------------------------------------------------------------------------- /internal-packages/docs-scraper/scripts/utils/NextConfig.ts: -------------------------------------------------------------------------------- 1 | export type NextConfig = { 2 | configName: string; 3 | urls: { 4 | type: 'app' | 'pages'; 5 | href: string; 6 | }[]; 7 | }; 8 | -------------------------------------------------------------------------------- /internal-packages/docs-scraper/scripts/utils/fromGithubAction.ts: -------------------------------------------------------------------------------- 1 | export function fromGithubAction(): boolean { 2 | return !!process.env['GH_ACTION']; 3 | } 4 | -------------------------------------------------------------------------------- /internal-packages/docs-scraper/scripts/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './NextConfig'; 2 | export * from './scrapeConfigs'; 3 | export * from './fromGithubAction'; 4 | -------------------------------------------------------------------------------- /internal-packages/docs-scraper/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cloudflare/next-on-pages-tsconfig/tsconfig.json", 3 | "include": ["scripts", ".eslintrc.js"] 4 | } 5 | -------------------------------------------------------------------------------- /internal-packages/eslint-config-next-on-pages/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2021: true, 4 | node: true, 5 | }, 6 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 7 | overrides: [], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | sourceType: 'module', 11 | project: true, 12 | }, 13 | plugins: ['@typescript-eslint', 'deprecation'], 14 | rules: { 15 | 'no-case-declarations': 'error', 16 | 'no-console': 'error', 17 | 'prefer-const': 'error', 18 | 'no-mixed-spaces-and-tabs': 'off', // off because it conflicts with prettier 19 | eqeqeq: 'error', 20 | '@typescript-eslint/no-explicit-any': 'error', 21 | '@typescript-eslint/no-unused-vars': 'error', 22 | '@typescript-eslint/consistent-type-imports': 'error', 23 | 24 | // Promises & async/await 25 | 'no-async-promise-executor': 'error', 26 | 'no-promise-executor-return': 'error', 27 | 'no-return-await': 'error', 28 | '@typescript-eslint/await-thenable': 'error', 29 | '@typescript-eslint/no-floating-promises': 'error', 30 | '@typescript-eslint/promise-function-async': 'error', 31 | 32 | 'deprecation/deprecation': 'error', 33 | }, 34 | ignorePatterns: ['vitest.config.ts'], 35 | }; 36 | -------------------------------------------------------------------------------- /internal-packages/eslint-config-next-on-pages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cloudflare/eslint-config-next-on-pages", 3 | "private": true, 4 | "description": "Shared eslint config for next-on-pages packages", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@typescript-eslint/eslint-plugin": "^5.58.0", 8 | "@typescript-eslint/parser": "^5.58.0", 9 | "eslint-plugin-deprecation": "^1.4.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal-packages/next-dev/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@cloudflare/eslint-config-next-on-pages'], 4 | rules: { 5 | 'no-console': 'off', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /internal-packages/next-dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cloudflare/next-on-pages-next-dev", 3 | "private": true, 4 | "main": "dist/index.cjs", 5 | "scripts": { 6 | "lint": "eslint src", 7 | "types-check": "tsc --noEmit", 8 | "build:js": "esbuild --bundle --format=cjs ./src/index.ts --external:miniflare --external:wrangler --external:@cspotcode/source-map-support --outfile=./dist/index.cjs --platform=node", 9 | "build:types": "tsc --emitDeclarationOnly --declaration --outDir ./dist", 10 | "build:js:watch": "npm run build:js -- --watch=forever", 11 | "build:types:watch": "npm run build:types -- --watch", 12 | "build": "npm run build:js && npm run build:types", 13 | "build:watch": "npm run build:js:watch & npm run build:types:watch", 14 | "test": "npx vitest --config vitest.config.ts" 15 | }, 16 | "files": [ 17 | "dist", 18 | "dev-init.cjs", 19 | "dev-init.d.ts", 20 | "devBindingsOptions.ts" 21 | ], 22 | "dependencies": { 23 | "wrangler": "^3.28.2", 24 | "miniflare": "^3.20231218.1" 25 | }, 26 | "devDependencies": { 27 | "@cloudflare/workers-types": "4.20231002.0", 28 | "@tsconfig/strictest": "^2.0.0", 29 | "esbuild": "^0.15.3", 30 | "eslint": "^8.35.0", 31 | "tsconfig": "*", 32 | "typescript": "^5.0.4", 33 | "vitest": "^0.32.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal-packages/next-dev/src/index.ts: -------------------------------------------------------------------------------- 1 | import { getPlatformProxy, type GetPlatformProxyOptions } from 'wrangler'; 2 | import { monkeyPatchVmModule, shouldSetupContinue } from './shared'; 3 | 4 | export * from './deprecated'; 5 | 6 | /** 7 | * Sets up the Cloudflare platform that need to be available during development time (using 8 | * Next.js' standard dev server) 9 | * 10 | * Note: the function is an async one but it doesn't need to be awaited 11 | * 12 | * @param options options how the function should operate and if/where to persist the platform data 13 | */ 14 | export async function setupDevPlatform( 15 | options?: GetPlatformProxyOptions, 16 | ): Promise { 17 | const continueSetup = shouldSetupContinue(); 18 | if (!continueSetup) return; 19 | 20 | monkeyPatchVmModule(await getPlatformProxy(options)); 21 | } 22 | -------------------------------------------------------------------------------- /internal-packages/next-dev/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cloudflare/next-on-pages-tsconfig/tsconfig.json", 3 | "include": [".eslintrc.js", "src", "dev-init.cjs", "devBindingsOptions.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /internal-packages/next-on-pages-tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cloudflare/next-on-pages-tsconfig", 3 | "private": true, 4 | "description": "Shared tsconfig for next-on-pages packages" 5 | } 6 | -------------------------------------------------------------------------------- /internal-packages/next-on-pages-tsconfig/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@tsconfig/strictest/tsconfig.json", 4 | "compilerOptions": { 5 | "target": "ES2022", 6 | "module": "CommonJS", 7 | "lib": ["ES2022"], 8 | "types": ["@cloudflare/workers-types"], 9 | 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true, 12 | "checkJs": false, 13 | 14 | "exactOptionalPropertyTypes": false, 15 | 16 | "outDir": "dist" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-on-pages-monorepo", 3 | "private": true, 4 | "workspaces": [ 5 | "internal-packages/*", 6 | "packages/*", 7 | "pages-e2e", 8 | "pages-e2e/features/*" 9 | ], 10 | "scripts": { 11 | "prettier": "prettier --ignore-path .prettierignore --ignore-path .gitignore .", 12 | "prettier__check": "npm run prettier -- --check", 13 | "prettier:check": "FORCE_COLOR=1 turbo run prettier__check", 14 | "prettier__fix": "npm run prettier -- --write", 15 | "prettier:fix": "FORCE_COLOR=1 turbo prettier__fix", 16 | "lint": "FORCE_COLOR=1 turbo lint", 17 | "types-check": "FORCE_COLOR=1 turbo types-check", 18 | "build": "FORCE_COLOR=1 turbo build", 19 | "build:watch": "FORCE_COLOR=1 turbo build:watch", 20 | "test:unit": "FORCE_COLOR=1 turbo test:unit", 21 | "pretest:e2e": "npm run build -- --filter next-on-pages", 22 | "test:e2e": "FORCE_COLOR=1 turbo test:e2e --filter pages-e2e", 23 | "publish": "turbo build & changeset publish", 24 | "changeset": "npx changeset" 25 | }, 26 | "devDependencies": { 27 | "prettier": "^3.0.0", 28 | "turbo": "^1.10.15" 29 | }, 30 | "engines": { 31 | "node": "20.10.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@cloudflare/eslint-config-next-on-pages'], 4 | }; 5 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Cloudflare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/docs/rules/no-app-nodejs-dynamic-ssg.md: -------------------------------------------------------------------------------- 1 | # `next-on-pages/no-app-nodejs-dynamic-ssg` 2 | 3 | When using [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) you need to either: 4 | 5 | - export [`dynamicParams`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams) set to `false` 6 | - export [`runtime`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#runtime) set to `true` 7 | 8 | This rule makes sure that if you're using `generateStaticParams` at least one of the two export is present. 9 | 10 | For more details refer to the [official Cloudflare Next.js docs](https://developers.cloudflare.com/pages/framework-guides/nextjs/ssr/troubleshooting/#generatestaticparams). 11 | 12 | ## ❌ Invalid Code 13 | 14 | ```js 15 | export async function generateStaticParams() { 16 | ~~~~~~~~~~~~~~~~~~~~ 17 | // ... 18 | } 19 | 20 | // ... 21 | ``` 22 | 23 | ## ✅ Valid Code 24 | 25 | ```js 26 | export const runtime = 'edge'; 27 | 28 | export async function generateStaticParams() { 29 | // ... 30 | } 31 | 32 | // ... 33 | ``` 34 | 35 | ```js 36 | export const dynamicParams = false; 37 | 38 | export async function generateStaticParams() { 39 | // ... 40 | } 41 | 42 | // ... 43 | ``` 44 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/docs/rules/no-nodejs-runtime.md: -------------------------------------------------------------------------------- 1 | # `next-on-pages/no-nodejs-runtime` 2 | 3 | Only the edge runtime is available when using `@cloudflare/next-on-pages`, this rule makes sure that you never try to 4 | set the `nodejs` runtime since that is going to break the application's build. 5 | 6 | ## ❌ Invalid Code 7 | 8 | ```js 9 | export const runtime = 'nodejs'; 10 | ~~~~~~ 11 | ``` 12 | 13 | ## ✅ Valid Code 14 | 15 | ```js 16 | export const runtime = 'edge'; 17 | ``` 18 | 19 | ```js 20 | export const runtime = 'experimental-edge'; 21 | ``` 22 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/src/index.ts: -------------------------------------------------------------------------------- 1 | import noNodeJsRuntime from './rules/no-nodejs-runtime'; 2 | import noUnsupportedConfigs from './rules/no-unsupported-configs'; 3 | import noAppNodejsDynamicSSG from './rules/no-app-nodejs-dynamic-ssg'; 4 | import noPagesNodejsDynamicSSG from './rules/no-pages-nodejs-dynamic-ssg'; 5 | 6 | import type { ESLint } from 'eslint'; 7 | 8 | const config: ESLint.Plugin = { 9 | rules: { 10 | 'no-nodejs-runtime': noNodeJsRuntime, 11 | 'no-unsupported-configs': noUnsupportedConfigs, 12 | 'no-app-nodejs-dynamic-ssg': noAppNodejsDynamicSSG, 13 | 'no-pages-nodejs-dynamic-ssg': noPagesNodejsDynamicSSG, 14 | 15 | // the following rule is no longer needed/applicable, it has been converted into a noop (so that it doesn't introduce a breaking change) 16 | // it should be removed in the next package major release 17 | 'no-app-not-found-runtime': () => ({}), 18 | }, 19 | configs: { 20 | recommended: { 21 | plugins: ['eslint-plugin-next-on-pages'], 22 | rules: { 23 | 'next-on-pages/no-nodejs-runtime': 'error', 24 | 'next-on-pages/no-unsupported-configs': 'error', 25 | 'next-on-pages/no-app-nodejs-dynamic-ssg': 'error', 26 | 'next-on-pages/no-pages-nodejs-dynamic-ssg': 'error', 27 | }, 28 | }, 29 | }, 30 | }; 31 | 32 | export = config; 33 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/src/rules/no-nodejs-runtime.ts: -------------------------------------------------------------------------------- 1 | import type { Rule } from 'eslint'; 2 | 3 | const rule: Rule.RuleModule = { 4 | create: context => { 5 | return { 6 | ExportNamedDeclaration: node => { 7 | const declaration = node.declaration; 8 | if ( 9 | declaration?.type === 'VariableDeclaration' && 10 | declaration.declarations.length === 1 && 11 | declaration.declarations[0]?.id.type === 'Identifier' && 12 | declaration.declarations[0].id.name === 'runtime' && 13 | declaration.declarations[0].init?.type === 'Literal' && 14 | declaration.declarations[0].init?.value === 'nodejs' 15 | ) { 16 | context.report({ 17 | message: 18 | "The 'nodejs' runtime is not supported. Use 'edge' instead.", 19 | node: declaration.declarations[0].init, 20 | fix: fixer => 21 | declaration.declarations[0]?.init 22 | ? fixer.replaceText(declaration.declarations[0].init, "'edge'") 23 | : null, 24 | }); 25 | } 26 | }, 27 | }; 28 | }, 29 | meta: { 30 | fixable: 'code', 31 | docs: { 32 | url: 'https://github.com/cloudflare/next-on-pages/blob/main/packages/eslint-plugin-next-on-pages/docs/rules/no-nodejs-runtime.md', 33 | }, 34 | }, 35 | }; 36 | 37 | export = rule; 38 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/src/utils/extract-paths.ts: -------------------------------------------------------------------------------- 1 | export function extractPaths(fullPath: string): string[] { 2 | const paths: string[] = []; 3 | const sections = fullPath.split('/'); 4 | 5 | if (sections.length === 1) return paths; 6 | 7 | if (sections[0]) { 8 | paths.push(sections[0]); 9 | } 10 | 11 | sections.slice(1, -1).forEach((section, i) => { 12 | paths.push(`${paths[i]}/${section}`); 13 | }); 14 | 15 | return paths; 16 | } 17 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/tests/rules/no-nodejs-runtime.test.ts: -------------------------------------------------------------------------------- 1 | import { RuleTester } from 'eslint'; 2 | 3 | import rule from '../../src/rules/no-nodejs-runtime'; 4 | import { describe, test } from 'vitest'; 5 | 6 | const tester = new RuleTester({ 7 | parser: require.resolve('@typescript-eslint/parser'), 8 | }); 9 | 10 | describe('no-nodejs-runtime', () => { 11 | test('should work', () => { 12 | tester.run('no-nodejs-runtime', rule, { 13 | valid: [ 14 | { code: `export const runtime = 'edge';` }, 15 | { code: `export const runtime = 'edge';` }, 16 | ], 17 | invalid: [ 18 | { 19 | code: `export const runtime = 'nodejs';`, 20 | errors: [ 21 | { 22 | message: 23 | "The 'nodejs' runtime is not supported. Use 'edge' instead.", 24 | column: "export const runtime = '".length, 25 | endColumn: 26 | "export const runtime = '".length + "nodejs'".length + 1, 27 | suggestions: [], 28 | }, 29 | ], 30 | output: "export const runtime = 'edge';", 31 | }, 32 | ], 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/tests/utils/extract-paths.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { extractPaths } from '../../src/utils/extract-paths'; 3 | 4 | describe('splitPaths', () => { 5 | test('should return an empty array if the input is empty', () => { 6 | expect(extractPaths('')).toEqual([]); 7 | }); 8 | 9 | test(`should return an empty array if the input doesn't present any '/' character`, () => { 10 | expect(extractPaths('abc')).toEqual([]); 11 | }); 12 | 13 | test(`should return a one-element array if the input contains a single '/' character`, () => { 14 | expect(extractPaths('a/b')).toEqual(['a']); 15 | }); 16 | 17 | test(`should return a two-elements array if the input contains a two '/' characters`, () => { 18 | expect(extractPaths('a/b/c')).toEqual(['a', 'a/b']); 19 | }); 20 | 21 | test(`should return a three-elements array if the input contains a three '/' characters`, () => { 22 | expect(extractPaths('a/b/c/d')).toEqual(['a', 'a/b', 'a/b/c']); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cloudflare/next-on-pages-tsconfig/tsconfig.json", 3 | "exclude": ["vitest.config.ts", ".eslintrc.js"], 4 | "compilerOptions": { 5 | "types": ["@types/node", "@types/estree-jsx"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/eslint-plugin-next-on-pages/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: ['tests/**/*.test.ts'], 6 | environment: 'node', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/next-on-pages/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@cloudflare/eslint-config-next-on-pages'], 4 | }; 5 | -------------------------------------------------------------------------------- /packages/next-on-pages/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Cloudflare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/next-on-pages/bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { spawn } = require('child_process'); 3 | const { join } = require('path'); 4 | 5 | spawn( 6 | process.execPath, 7 | [ 8 | ...process.execArgv, 9 | join(__dirname, '..', 'dist', 'index.js'), 10 | ...process.argv.slice(2), 11 | ], 12 | { stdio: 'inherit' }, 13 | ).on('exit', code => 14 | process.exit(code === undefined || code === null ? 0 : code), 15 | ); 16 | -------------------------------------------------------------------------------- /packages/next-on-pages/build-metadata.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Metadata generated by the next-on-pages build process 3 | */ 4 | type NextOnPagesBuildMetadata = { 5 | /** Locales used by the application (collected from the Vercel output) */ 6 | collectedLocales: string[]; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/next-on-pages/build-no-nodejs-compat-flag-static-error-page.mjs: -------------------------------------------------------------------------------- 1 | import { readdir, readFile, writeFile, mkdir } from 'fs/promises'; 2 | 3 | import imageToBase64 from 'image-to-base64'; 4 | 5 | const errorPagePath = './no-nodejs-compat-flag-static-error-page'; 6 | 7 | let indexHtmlContent = await readFile(`./${errorPagePath}/assets/index.html`, { 8 | encoding: 'utf-8', 9 | }); 10 | 11 | const imgs = await readdir(`./${errorPagePath}/assets/img`); 12 | 13 | await Promise.all( 14 | imgs.map(async img => { 15 | const base64Data = await imageToBase64( 16 | `./${errorPagePath}/assets/img/${img}`, 17 | ); 18 | indexHtmlContent = indexHtmlContent.replace( 19 | `src="./img/${img}"`, 20 | `src="data:image/png;base64,${base64Data}"`, 21 | ); 22 | }), 23 | ); 24 | 25 | await mkdir(`./${errorPagePath}/dist`, { recursive: true }); 26 | await writeFile(`./${errorPagePath}/dist/index.html`, indexHtmlContent); 27 | -------------------------------------------------------------------------------- /packages/next-on-pages/env.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | namespace NodeJS { 3 | interface ProcessEnv { 4 | NODE_ENV?: string; 5 | npm_config_user_agent?: string; 6 | CF_PAGES?: string; 7 | SHELL?: string; 8 | __NEXT_ON_PAGES__KV_SUSPENSE_CACHE?: KVNamespace; 9 | [key: string]: string | Fetcher; 10 | } 11 | } 12 | } 13 | 14 | export {}; 15 | -------------------------------------------------------------------------------- /packages/next-on-pages/no-nodejs-compat-flag-static-error-page/assets/img/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/next-on-pages/3d84ab0096258febb88193c4b9d269ecbaf1b19b/packages/next-on-pages/no-nodejs-compat-flag-static-error-page/assets/img/input.png -------------------------------------------------------------------------------- /packages/next-on-pages/no-nodejs-compat-flag-static-error-page/assets/img/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/next-on-pages/3d84ab0096258febb88193c4b9d269ecbaf1b19b/packages/next-on-pages/no-nodejs-compat-flag-static-error-page/assets/img/result.png -------------------------------------------------------------------------------- /packages/next-on-pages/src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getRequestContext'; 2 | -------------------------------------------------------------------------------- /packages/next-on-pages/src/buildApplication/index.ts: -------------------------------------------------------------------------------- 1 | export * from './buildApplication'; 2 | -------------------------------------------------------------------------------- /packages/next-on-pages/src/buildApplication/processUtils.ts: -------------------------------------------------------------------------------- 1 | import type { spawn } from 'node:child_process'; 2 | 3 | /** 4 | * Waits for a spawned process to close. 5 | * 6 | * @param spawnedProcess Spawned process to wait for. 7 | * @returns Promise that resolves when the process closes with code 0, or rejects when the process 8 | * closes with a non-zero code. 9 | */ 10 | export async function waitForProcessToClose( 11 | spawnedProcess: ReturnType, 12 | ): Promise { 13 | return new Promise((resolve, reject) => { 14 | spawnedProcess.on('close', code => { 15 | if (code === 0) { 16 | resolve(); 17 | } else { 18 | reject(); 19 | } 20 | }); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /packages/next-on-pages/src/fetch-handler/index.ts: -------------------------------------------------------------------------------- 1 | import 'server-only'; 2 | 3 | export default { 4 | async fetch() { 5 | throw new Error( 6 | 'Invalid invocation of the next-on-pages fetch handler - this method should only be used alongside the --custom-entrypoint CLI option. For more details, see: https://github.com/cloudflare/next-on-pages/blob/main/packages/next-on-pages/docs/advanced-usage.md#custom-entrypoint', 7 | ); 8 | }, 9 | } as { fetch: ExportedHandlerFetchHandler<{ ASSETS: Fetcher }> }; 10 | -------------------------------------------------------------------------------- /packages/next-on-pages/src/utils/getNodeEnv.ts: -------------------------------------------------------------------------------- 1 | import { cliWarn } from '../cli'; 2 | 3 | enum NextJsNodeEnv { 4 | PRODUCTION = 'production', 5 | DEVELOPMENT = 'development', 6 | TEST = 'test', 7 | } 8 | 9 | export function getNodeEnv(): string { 10 | const processNodeEnv = process.env.NODE_ENV; 11 | 12 | if (!processNodeEnv) { 13 | return NextJsNodeEnv.PRODUCTION; 14 | } 15 | 16 | const nextJsNodeEnvs = Object.values(NextJsNodeEnv); 17 | if (!(nextJsNodeEnvs as string[]).includes(processNodeEnv)) { 18 | cliWarn( 19 | ` 20 | WARNING: 21 | The current value of the environment variable NODE_ENV is "${processNodeEnv}", 22 | but the supported values are: ${nextJsNodeEnvs 23 | .map(env => `"${env}"`) 24 | .join(', ')}. 25 | See: https://nextjs.org/docs/basic-features/environment-variables. 26 | `, 27 | { spaced: true }, 28 | ); 29 | } 30 | 31 | return processNodeEnv; 32 | } 33 | -------------------------------------------------------------------------------- /packages/next-on-pages/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fs'; 2 | export * from './version'; 3 | export * from './routing'; 4 | export * from './str'; 5 | export * from './os'; 6 | export * from './packages'; 7 | -------------------------------------------------------------------------------- /packages/next-on-pages/src/utils/os.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks whether the current platform is Windows. 3 | * 4 | * @returns Whether the current platform is Windows. 5 | */ 6 | export function isWindows(): boolean { 7 | return process.platform === 'win32'; 8 | } 9 | -------------------------------------------------------------------------------- /packages/next-on-pages/src/utils/packages.ts: -------------------------------------------------------------------------------- 1 | import type { PackageManager } from 'package-manager-manager'; 2 | 3 | /** 4 | * Utility that retrieves the version of an installed package. In case the package is not installed or 5 | * retrieving the version of the package generated errors null is returned instead. 6 | * 7 | * @param pm package manager object to use 8 | * @param packageName name of the package 9 | * @returns the version of the installed package if it was successfully detected, null otherwise 10 | */ 11 | export async function getPackageVersionOrNull( 12 | pm: PackageManager, 13 | packageName: string, 14 | ): Promise { 15 | const packageInfo = await pm.getPackageInfo(packageName).catch(() => null); 16 | return packageInfo?.version ?? null; 17 | } 18 | -------------------------------------------------------------------------------- /packages/next-on-pages/src/utils/requestContext.ts: -------------------------------------------------------------------------------- 1 | export type RequestContext = { 2 | request: Request; 3 | assetsFetcher: Fetcher; 4 | ctx: ExecutionContext; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/next-on-pages/src/utils/str.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Replaces the last instance of a string, in a string. 3 | * 4 | * @param contents Contents of the file to replace the last instance in. 5 | * @param target The target string to replace. 6 | * @param value The value to replace the target with. 7 | * @returns The updated contents. 8 | */ 9 | export function replaceLastSubstringInstance( 10 | contents: string, 11 | target: string, 12 | value: string, 13 | ): string { 14 | const lastIndex = contents.lastIndexOf(target); 15 | 16 | if (lastIndex === -1) { 17 | return contents; 18 | } 19 | 20 | return ( 21 | contents.slice(0, lastIndex) + 22 | value + 23 | contents.slice(lastIndex + target.length) 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/next-on-pages/src/utils/version.ts: -------------------------------------------------------------------------------- 1 | import rawPackageJson from '../../package.json'; 2 | 3 | const packageJson = rawPackageJson as unknown as { 4 | version: string; 5 | nextOnPagesMetadata?: NextOnPagesMetadata; 6 | }; 7 | 8 | /** Current version of the @cloudflare/next-on-pages package (in a helpful human readable form) */ 9 | export const nextOnPagesVersion = `${ 10 | packageJson.version 11 | }${getVersionExtraInfo()}`; 12 | 13 | type NextOnPagesMetadata = { pullRequest?: number; beta?: boolean }; 14 | 15 | function getVersionExtraInfo(): string { 16 | const { nextOnPagesMetadata: { pullRequest, beta } = {} } = packageJson; 17 | 18 | if (pullRequest) { 19 | return ` (prerelease for PR #${pullRequest})`; 20 | } 21 | 22 | if (beta) { 23 | return ' (beta/canary release)'; 24 | } 25 | 26 | return ''; 27 | } 28 | -------------------------------------------------------------------------------- /packages/next-on-pages/templates/_worker.js/utils/doImport.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Newer versions of esbuild try to resolve dynamic imports by crawling the filesystem for matching modules. 3 | * This doesn't work with the code next-on-pages generates, because the modules don't necessarily exist on the filesystem. 4 | * This forces esbuild _not_ to look for matching modules on the filesystem (because esbuild doesn't inline functions when minifying) 5 | */ 6 | export async function doImport(m: string) { 7 | return import(m); 8 | } 9 | -------------------------------------------------------------------------------- /packages/next-on-pages/templates/_worker.js/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './matcher'; 2 | export * from './request'; 3 | export * from './http'; 4 | export * from './pcre'; 5 | export * from './routing'; 6 | export * from './images'; 7 | export * from './fetch'; 8 | -------------------------------------------------------------------------------- /packages/next-on-pages/templates/_worker.js/utils/request.ts: -------------------------------------------------------------------------------- 1 | import { SUSPENSE_CACHE_URL } from '../../cache'; 2 | 3 | /** 4 | * Adjusts the request so that it is formatted as if it were provided by Vercel 5 | * 6 | * @param request the original request received by the worker 7 | * @returns the adjusted request to pass to Next 8 | */ 9 | export function adjustRequestForVercel(request: Request): Request { 10 | const adjustedHeaders = new Headers(request.headers); 11 | 12 | if (request.cf) { 13 | adjustedHeaders.set( 14 | 'x-vercel-ip-city', 15 | encodeURIComponent(request.cf.city as string), 16 | ); 17 | adjustedHeaders.set('x-vercel-ip-country', request.cf.country as string); 18 | adjustedHeaders.set( 19 | 'x-vercel-ip-country-region', 20 | request.cf.regionCode as string, 21 | ); 22 | adjustedHeaders.set('x-vercel-ip-latitude', request.cf.latitude as string); 23 | adjustedHeaders.set( 24 | 'x-vercel-ip-longitude', 25 | request.cf.longitude as string, 26 | ); 27 | } 28 | 29 | adjustedHeaders.set('x-vercel-sc-host', SUSPENSE_CACHE_URL); 30 | 31 | return new Request(request, { headers: adjustedHeaders }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/next-on-pages/templates/cache/cache-api.ts: -------------------------------------------------------------------------------- 1 | import { CacheAdaptor } from './adaptor.js'; 2 | 3 | /** Suspense Cache adaptor for the Cache API. */ 4 | export default class CacheApiAdaptor extends CacheAdaptor { 5 | /** Name of the cache to open in the Cache API. */ 6 | public cacheName = 'suspense-cache'; 7 | 8 | constructor(ctx: Record = {}) { 9 | super(ctx); 10 | } 11 | 12 | public override async retrieve(key: string) { 13 | const cache = await caches.open(this.cacheName); 14 | 15 | const response = await cache.match(this.buildCacheKey(key)); 16 | return response ? response.text() : null; 17 | } 18 | 19 | public override async update( 20 | key: string, 21 | value: string, 22 | revalidate?: number, 23 | ) { 24 | const cache = await caches.open(this.cacheName); 25 | 26 | const maxAge = revalidate ?? '31536000'; // 1 year 27 | const response = new Response(value, { 28 | headers: new Headers({ 29 | 'cache-control': `max-age=${maxAge}`, 30 | }), 31 | }); 32 | await cache.put(this.buildCacheKey(key), response); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/next-on-pages/templates/cache/index.ts: -------------------------------------------------------------------------------- 1 | export * from './adaptor'; 2 | -------------------------------------------------------------------------------- /packages/next-on-pages/templates/cache/kv.ts: -------------------------------------------------------------------------------- 1 | import { CacheAdaptor } from './adaptor.js'; 2 | 3 | /** Suspense Cache adaptor for Workers KV. */ 4 | export default class KVAdaptor extends CacheAdaptor { 5 | constructor(ctx: Record = {}) { 6 | super(ctx); 7 | } 8 | 9 | public override async retrieve(key: string) { 10 | const value = await process.env.__NEXT_ON_PAGES__KV_SUSPENSE_CACHE?.get( 11 | this.buildCacheKey(key), 12 | ); 13 | 14 | return value ?? null; 15 | } 16 | 17 | public override async update( 18 | key: string, 19 | value: string, 20 | revalidate?: number, 21 | ) { 22 | const expiry = revalidate 23 | ? { 24 | expirationTtl: revalidate, 25 | } 26 | : {}; 27 | 28 | await process.env.__NEXT_ON_PAGES__KV_SUSPENSE_CACHE?.put( 29 | this.buildCacheKey(key), 30 | value, 31 | expiry, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/next-on-pages/tests/src/utils/str.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { replaceLastSubstringInstance } from '../../../src/utils'; 3 | 4 | describe('replaceLastSubstringInstance', () => { 5 | test('should replace last instance of a string, in a string', () => { 6 | const input = 'one two one two'; 7 | 8 | let newValue = replaceLastSubstringInstance(input, 'one', 'three'); 9 | expect(newValue).toEqual('one two three two'); 10 | 11 | newValue = replaceLastSubstringInstance(newValue, 'one', 'four'); 12 | expect(newValue).toEqual('four two three two'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/next-on-pages/tsconfig.api.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cloudflare/next-on-pages-tsconfig/tsconfig.json", 3 | "include": ["src/api"], 4 | "compilerOptions": { 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "outDir": "dist/api" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/next-on-pages/tsconfig.fetch-handler.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cloudflare/next-on-pages-tsconfig/tsconfig.json", 3 | "include": ["src/fetch-handler"], 4 | "compilerOptions": { 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "outDir": "dist/fetch-handler" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/next-on-pages/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@cloudflare/next-on-pages-tsconfig/tsconfig.json", 3 | "include": [ 4 | "src", 5 | "templates", 6 | "tests", 7 | "vercel.types.d.ts", 8 | "build-metadata.d.ts", 9 | "env.d.ts", 10 | ".eslintrc.js", 11 | "build-no-nodejs-compat-flag-static-error-page.mjs" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/next-on-pages/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: ['tests/**/*.test.ts'], 6 | environment: 'miniflare', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /pages-e2e/.env.example: -------------------------------------------------------------------------------- 1 | CLOUDFLARE_API_TOKEN= 2 | CLOUDFLARE_ACCOUNT_ID=5a883b414d4090a1442b20361f3c43a9 3 | PROJECT_NAME=pages-e2e-tests-tmp -------------------------------------------------------------------------------- /pages-e2e/features/_utils/copyWorkspaceAssets.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { cp, readdir } from 'fs/promises'; 4 | import { join } from 'path'; 5 | 6 | /** 7 | * Copies all the files present in the `./assets` folder into the 8 | * WORKSPACE_DIR folder (merging potentially already existing directories) 9 | */ 10 | export async function copyWorkspaceAssets(): Promise { 11 | const { WORKSPACE_DIR } = process.env; 12 | 13 | const assets = await readdir(join(process.cwd(), 'assets')); 14 | 15 | for (const asset of assets) { 16 | await cp(join(process.cwd(), 'assets', asset), join(WORKSPACE_DIR, asset), { 17 | recursive: true, 18 | force: false, 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pages-e2e/features/_utils/runWithHardNavigationsChecking.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from 'playwright'; 2 | 3 | type HardNavigation = { url: string }; 4 | 5 | export async function runWithHardNavigationsChecking( 6 | page: Page, 7 | testSnippet: () => Promise, 8 | hardNavigationsCheck: (hardNavigations: HardNavigation[]) => Promise, 9 | ): Promise { 10 | const hardNavigations: HardNavigation[] = []; 11 | 12 | page.on('domcontentloaded', () => { 13 | hardNavigations.push({ url: page.url() }); 14 | }); 15 | 16 | await testSnippet(); 17 | 18 | await hardNavigationsCheck(hardNavigations); 19 | } 20 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/assets/app/api/configs-headers/not-to-apply/some-route/route.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export async function GET(request) { 4 | return new Response('api/configs-headers/not-to-apply/some-route route'); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/assets/app/api/configs-headers/to-apply/some-route/route.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export async function GET(request) { 4 | return new Response('api/configs-headers/to-apply/some-route route'); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/assets/app/configs-rewrites/dynamic/[page]/page.jsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default async function SomePage() { 4 | const message = await getServerSideMessage(); 5 | 6 | return ( 7 | <> 8 |

{message}

9 | 10 | ); 11 | } 12 | 13 | async function getServerSideMessage() { 14 | return 'This is a dynamic (configs-rewrites) page'; 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/assets/app/configs-rewrites/header-somewhere-else/page.jsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default async function SomePage() { 4 | const message = await getServerSideMessage(); 5 | 6 | return ( 7 | <> 8 |

{message}

9 | 10 | ); 11 | } 12 | 13 | async function getServerSideMessage() { 14 | return 'This is the "header-somewhere-else" page'; 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/assets/app/configs-rewrites/query-somewhere-else/page.jsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default async function SomePage() { 4 | const message = await getServerSideMessage(); 5 | 6 | return ( 7 | <> 8 |

{message}

9 | 10 | ); 11 | } 12 | 13 | async function getServerSideMessage() { 14 | return 'This is the "query-somewhere-else" page'; 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/assets/app/configs-rewrites/rewritten-wildcard/my-page/page.jsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default async function SomePage() { 4 | const message = await getServerSideMessage(); 5 | 6 | return ( 7 | <> 8 |

{message}

9 | 10 | ); 11 | } 12 | 13 | async function getServerSideMessage() { 14 | return 'This is the "rewritten-wildcard/my-page" page'; 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/assets/app/configs-rewrites/some-page/page.jsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default async function SomePage() { 4 | const message = await getServerSideMessage(); 5 | 6 | return ( 7 | <> 8 |

{message}

9 |

This page is static

10 | 11 | ); 12 | } 13 | 14 | async function getServerSideMessage() { 15 | return 'This is the "some-page" page'; 16 | } 17 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/assets/app/configs-rewrites/wildcard/my-page/page.jsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default async function SomePage() { 4 | const message = await getServerSideMessage(); 5 | 6 | return ( 7 | <> 8 |

{message}

9 | 10 | ); 11 | } 12 | 13 | async function getServerSideMessage() { 14 | return 'This is the "wildcard/my-page" page'; 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/headers.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | 3 | describe('next.config.mjs Headers', () => { 4 | test('addition of headers to api response', async ({ expect }) => { 5 | const response = await fetch( 6 | `${DEPLOYMENT_URL}/api/configs-headers/to-apply/some-route`, 7 | ); 8 | expect(await response.text()).toBe( 9 | 'api/configs-headers/to-apply/some-route route', 10 | ); 11 | expect(response.headers.get('x-custom-configs-header')).toEqual( 12 | 'my custom header value (from next.config.mjs)', 13 | ); 14 | expect(response.headers.get('x-another-custom-configs-header')).toEqual( 15 | 'my other custom header value (from next.config.mjs)', 16 | ); 17 | }); 18 | 19 | test('no addition of headers to api response', async ({ expect }) => { 20 | const response = await fetch( 21 | `${DEPLOYMENT_URL}/api/configs-headers/not-to-apply/some-route`, 22 | ); 23 | expect(await response.text()).toBe( 24 | 'api/configs-headers/not-to-apply/some-route route', 25 | ); 26 | expect(response.headers.get('x-custom-configs-header')).toBe(null); 27 | expect(response.headers.get('x-another-custom-configs-header')).toBe(null); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/headers.ts: -------------------------------------------------------------------------------- 1 | export function headers() { 2 | return [ 3 | { 4 | source: '/api/configs-headers/to-apply/:path*', 5 | headers: [ 6 | { 7 | key: 'x-custom-configs-header', 8 | value: 'my custom header value (from next.config.mjs)', 9 | }, 10 | { 11 | key: 'x-another-custom-configs-header', 12 | value: 'my other custom header value (from next.config.mjs)', 13 | }, 14 | ], 15 | }, 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/redirects.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | 3 | describe('next.config.mjs Redirects', () => { 4 | test('basic non-permanent redirects', async ({ expect }) => { 5 | const response = await fetch( 6 | `${DEPLOYMENT_URL}/permanent-config-redirect`, 7 | { redirect: 'manual' }, 8 | ); 9 | expect(response.status).toBe(308); 10 | expect(response.headers.get('location')).toMatch( 11 | /\/permanent-config-redirect-destination$/, 12 | ); 13 | }); 14 | 15 | test('basic permanent redirects', async ({ expect }) => { 16 | const response = await fetch( 17 | `${DEPLOYMENT_URL}/non-permanent-config-redirect`, 18 | { redirect: 'manual' }, 19 | ); 20 | expect(response.status).toBe(307); 21 | expect(response.headers.get('location')).toMatch( 22 | /\/non-permanent-config-redirect-destination$/, 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/redirects.ts: -------------------------------------------------------------------------------- 1 | export function redirects() { 2 | return [ 3 | { 4 | source: '/permanent-config-redirect', 5 | destination: '/permanent-config-redirect-destination', 6 | permanent: true, 7 | }, 8 | { 9 | source: '/non-permanent-config-redirect', 10 | destination: '/non-permanent-config-redirect-destination', 11 | permanent: false, 12 | }, 13 | ]; 14 | } 15 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsRewritesRedirectsHeaders/rewrites.ts: -------------------------------------------------------------------------------- 1 | export function rewrites() { 2 | return { 3 | beforeFiles: [ 4 | { 5 | source: '/configs-rewrites/some-page', 6 | destination: '/configs-rewrites/query-somewhere-else', 7 | has: [{ type: 'query', key: 'overrideMe' }], 8 | }, 9 | { 10 | source: '/configs-rewrites/some-page', 11 | destination: '/configs-rewrites/header-somewhere-else', 12 | has: [{ type: 'header', key: 'overrideMe' }], 13 | }, 14 | { 15 | source: '/configs-rewrites/some-page', 16 | destination: '/configs-rewrites/header-somewhere-else', 17 | has: [{ type: 'header', key: 'overrideMe' }], 18 | }, 19 | { 20 | source: '/configs-rewrites/wildcard/:slug*', 21 | destination: '/configs-rewrites/rewritten-wildcard/:slug*', 22 | }, 23 | ], 24 | afterFiles: [ 25 | { 26 | source: '/configs-rewrites/some-page', 27 | destination: '/configs-rewrites/header-somewhere-else', 28 | }, 29 | { 30 | source: '/configs-rewrites/dynamic/:path*', 31 | destination: '/configs-rewrites/some-page', 32 | }, 33 | ], 34 | fallback: [ 35 | { 36 | source: '/configs-rewrites/:path*', 37 | destination: '/configs-rewrites/some-page', 38 | }, 39 | ], 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsTrailingSlashFalse/assets/app/api/routing-trailing-slash-test/route.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export async function GET(request) { 4 | return new Response('Hello world'); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsTrailingSlashFalse/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsTrailingSlashFalse/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | 4 | // Note: 5 | // There's no need to update the next.config.js file here as the 6 | // trailingSlash property is false by default 7 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsTrailingSlashFalse/trailingSlash.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | 3 | describe('default trailing slashes redirection', () => { 4 | describe('fetching with an added trailing slashed results in a redirected response', () => { 5 | test('the user gets the requested page', async ({ expect }) => { 6 | const response = await fetch( 7 | `${DEPLOYMENT_URL}/api/routing-trailing-slash-test/`, 8 | ); 9 | 10 | expect(response.redirected).toBe(true); 11 | expect(response.status).toBe(200); 12 | expect(response.url).toEqual( 13 | `${DEPLOYMENT_URL}/api/routing-trailing-slash-test`, 14 | ); 15 | }); 16 | 17 | test('the response is actually a redirect (and not a rewrite)', async ({ 18 | expect, 19 | }) => { 20 | const err = await fetch( 21 | `${DEPLOYMENT_URL}/api/routing-trailing-slash-test/`, 22 | { redirect: 'error' }, 23 | ) 24 | .then(() => null) 25 | .catch(e => e.cause.message); 26 | 27 | expect(err).toEqual('unexpected redirect'); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsTrailingSlashTrue/assets/app/api/routing-trailing-slash-test/route.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export async function GET(request) { 4 | return new Response('Hello world'); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsTrailingSlashTrue/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsTrailingSlashTrue/setup.ts: -------------------------------------------------------------------------------- 1 | import { writeFile, readFile } from 'fs/promises'; 2 | import { join } from 'path'; 3 | import * as recast from 'recast'; 4 | 5 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 6 | 7 | await copyWorkspaceAssets(); 8 | 9 | const { WORKSPACE_DIR } = process.env; 10 | 11 | const nextConfigJsPath = join(WORKSPACE_DIR, 'next.config.mjs'); 12 | 13 | const nextConfigJsSource = await readFile(nextConfigJsPath, 'utf-8'); 14 | 15 | const ast = recast.parse(nextConfigJsSource); 16 | 17 | recast.visit(ast, { 18 | visitVariableDeclaration: function (path) { 19 | if ( 20 | path?.value?.declarations.length === 1 && 21 | path.value.declarations[0].id.name === 'nextConfig' 22 | ) { 23 | const nextConfigAst = path.value.declarations[0].init; 24 | 25 | const { property, identifier, booleanLiteral } = recast.types.builders; 26 | 27 | var trailingSlashProp = property.from({ 28 | kind: 'init', 29 | key: identifier('trailingSlash'), 30 | value: booleanLiteral(true), 31 | }); 32 | 33 | nextConfigAst.properties.push(trailingSlashProp); 34 | } 35 | this.traverse(path); 36 | }, 37 | }); 38 | 39 | await writeFile(nextConfigJsPath, recast.print(ast).code); 40 | -------------------------------------------------------------------------------- /pages-e2e/features/appConfigsTrailingSlashTrue/trailingSlash.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | 3 | describe('default trailing slashes redirection', () => { 4 | describe('fetching without an added trailing slash results in a redirected response', () => { 5 | test('the user gets the requested page', async ({ expect }) => { 6 | const response = await fetch( 7 | `${DEPLOYMENT_URL}/api/routing-trailing-slash-test`, 8 | ); 9 | 10 | expect(response.redirected).toBe(true); 11 | expect(response.status).toBe(200); 12 | expect(response.url).toEqual( 13 | `${DEPLOYMENT_URL}/api/routing-trailing-slash-test/`, 14 | ); 15 | }); 16 | 17 | test('the response is actually a redirect (and not a rewrite)', async ({ 18 | expect, 19 | }) => { 20 | const err = await fetch( 21 | `${DEPLOYMENT_URL}/api/routing-trailing-slash-test`, 22 | { redirect: 'error' }, 23 | ) 24 | .then(() => null) 25 | .catch(e => e.cause.message); 26 | 27 | expect(err).toEqual('unexpected redirect'); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /pages-e2e/features/appGetRequestContext/assets/app/api/get-request-context/route.js: -------------------------------------------------------------------------------- 1 | import { getRequestContext } from '@cloudflare/next-on-pages'; 2 | 3 | export const runtime = 'edge'; 4 | 5 | export async function GET() { 6 | const { 7 | env: { MY_TOML_VAR: myTomlVar, MY_TOML_KV }, 8 | ctx, 9 | cf, 10 | } = getRequestContext(); 11 | 12 | await MY_TOML_KV.put('kv-key', 'kv-value'); 13 | const kvValue = await MY_TOML_KV.get('kv-key'); 14 | 15 | return Response.json({ 16 | myTomlVar, 17 | kvValue, 18 | typeofWaitUntil: typeof ctx.waitUntil, 19 | typeofCfColo: typeof cf.colo, 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /pages-e2e/features/appGetRequestContext/assets/app/get-request-context/page.jsx: -------------------------------------------------------------------------------- 1 | import { getRequestContext } from '@cloudflare/next-on-pages'; 2 | 3 | export const runtime = 'edge'; 4 | 5 | export default async function SimpleKvFormsPage() { 6 | const { 7 | env: { MY_TOML_VAR, MY_TOML_KV }, 8 | ctx, 9 | cf, 10 | } = getRequestContext(); 11 | 12 | await MY_TOML_KV.put('kv-key', 'kv-value'); 13 | const kvValue = await MY_TOML_KV.get('kv-key'); 14 | 15 | return ( 16 |
17 |

Server Component

18 |

MY_TOML_VAR = '{MY_TOML_VAR}'

19 |

the KV value is '{kvValue}'

20 |

21 | typeof ctx.waitUntil = '{typeof ctx.waitUntil}' 22 |

23 |

typeof cf.colo = '{typeof cf.colo}'

24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /pages-e2e/features/appGetRequestContext/assets/wrangler.toml: -------------------------------------------------------------------------------- 1 | [vars] 2 | MY_TOML_VAR = "my var from wrangler.toml" 3 | 4 | [[kv_namespaces]] 5 | binding = "MY_TOML_KV" 6 | id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 7 | -------------------------------------------------------------------------------- /pages-e2e/features/appGetRequestContext/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appGetRequestContext/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/appMiddleware/assets/app/api/middleware-test/hello/route.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export async function GET(request) { 4 | return new Response('Hello middleware-test'); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/features/appMiddleware/assets/app/api/middleware-test/unreachable/route.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export async function GET(request) { 4 | return new Response('ERROR: This route should not be reachable!'); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/features/appMiddleware/assets/app/middleware-test/pageA/page.jsx: -------------------------------------------------------------------------------- 1 | import { headers } from 'next/headers'; 2 | 3 | export default function Page() { 4 | const requestHeaders = []; 5 | headers().forEach((value, key) => { 6 | requestHeaders.push({ key, value }); 7 | }); 8 | 9 | return ( 10 |
11 |

Page A

12 |

Headers

13 |

The request contained the following headers:

14 |
    15 | {requestHeaders.map(({ key, value }) => ( 16 |
  • 17 | {key}: {value} 18 |
  • 19 | ))} 20 |
21 |
22 | ); 23 | } 24 | 25 | export const runtime = 'edge'; 26 | -------------------------------------------------------------------------------- /pages-e2e/features/appMiddleware/assets/app/middleware-test/pageB/page.jsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function Page() { 4 | const links = [ 5 | { 6 | href: '/middleware-test/pageC?redirect-to-page-a', 7 | text: 'go to page A via middleware redirect', 8 | }, 9 | { 10 | href: '/middleware-test/pageC?rewrite-to-page-a', 11 | text: 'go to page C which is page A served via middleware rewrite', 12 | }, 13 | ]; 14 | 15 | return ( 16 |
17 |

Page B

18 | {links.map(({ href, text }) => ( 19 |
20 | {text} 21 |
22 | ))} 23 |
24 | ); 25 | } 26 | 27 | export const runtime = 'edge'; 28 | -------------------------------------------------------------------------------- /pages-e2e/features/appMiddleware/assets/app/middleware-test/unreachable/page.jsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return

ERROR: This route should not be reachable!

; 3 | } 4 | 5 | export const runtime = 'edge'; 6 | -------------------------------------------------------------------------------- /pages-e2e/features/appMiddleware/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appMiddleware/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/appNoNodeJsCompatFlag/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appNoNodeJsCompatFlag/no-nodejs_compat-flag.test.ts: -------------------------------------------------------------------------------- 1 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 2 | import { describe, test } from 'vitest'; 3 | 4 | describe('no nodejs_compatibility flag set', () => { 5 | test('a request to the route of the application returns an actionable static error page', async ({ 6 | expect, 7 | }) => { 8 | const page = await BROWSER.newPage(); 9 | const assertVisible = getAssertVisible(page); 10 | 11 | const pageUrl = `${DEPLOYMENT_URL}/`; 12 | 13 | await page.goto(pageUrl); 14 | 15 | await assertVisible('.error', { hasText: 'Node.JS Compatibility Error' }); 16 | 17 | await assertVisible('.what-can-i-do > h2', { hasText: 'What can I do?' }); 18 | 19 | expect(page.url()).toEqual(pageUrl); 20 | }); 21 | 22 | test('a request to any sub-route of the application returns an actionable static error page', async ({ 23 | expect, 24 | }) => { 25 | const page = await BROWSER.newPage(); 26 | const assertVisible = getAssertVisible(page); 27 | 28 | const pageUrl = `${DEPLOYMENT_URL}/non-existing-route`; 29 | 30 | await page.goto(pageUrl); 31 | 32 | await assertVisible('.error', { hasText: 'Node.JS Compatibility Error' }); 33 | 34 | await assertVisible('.what-can-i-do > h2', { hasText: 'What can I do?' }); 35 | 36 | expect(page.url()).toEqual(pageUrl); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /pages-e2e/features/appNoNodeJsCompatFlag/setup.ts: -------------------------------------------------------------------------------- 1 | // no setup required 2 | -------------------------------------------------------------------------------- /pages-e2e/features/appRouting/404.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 3 | 4 | describe('standard not found pages', () => { 5 | ['/invalid-route', '/404', '/nested/non/existing/route'].map(path => 6 | test(`visiting ${path} results in the default 404 page`, async () => { 7 | const page = await BROWSER.newPage(); 8 | const assertVisible = getAssertVisible(page); 9 | 10 | await page.goto(`${DEPLOYMENT_URL}${path}`); 11 | 12 | await assertVisible('h1', { 13 | hasText: '404', 14 | }); 15 | 16 | await assertVisible('h2', { 17 | hasText: 'This page could not be found.', 18 | }); 19 | }), 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /pages-e2e/features/appRouting/500.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 3 | 4 | describe('default error page', () => { 5 | test(`visiting a page that throws results in the default server error page`, async () => { 6 | const page = await BROWSER.newPage(); 7 | const assertVisible = getAssertVisible(page); 8 | 9 | await page.goto(`${DEPLOYMENT_URL}/500-error`); 10 | 11 | await assertVisible('h2', { 12 | hasText: 'Application error', 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /pages-e2e/features/appRouting/assets/app/500-error/page.jsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | throw new Error('custom error'); 3 | 4 | return ( 5 |
6 |

Unaccessible page

7 |

this page shouldn't be accessible since the component throws

8 |
9 | ); 10 | } 11 | 12 | export const runtime = 'edge'; 13 | -------------------------------------------------------------------------------- /pages-e2e/features/appRouting/assets/app/ssg-dynamic/[slug]/page.jsx: -------------------------------------------------------------------------------- 1 | export const dynamicParams = false; 2 | 3 | export function generateStaticParams() { 4 | const slugs = ['foo', 'bar', 'baz']; 5 | return slugs.map(slug => ({ slug })); 6 | } 7 | 8 | export default function Page({ params }) { 9 | return ( 10 |
11 |

SSGed Dynamic Page

12 |

slug: {params.slug}

13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/features/appRouting/assets/app/ssr-dynamic/catch-all/[...pets]/page.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default function SSRDynamicCatchAllPage({ params }) { 4 | return ( 5 |
6 |

The provided pets are:

7 |
    8 | {params.pets.map((pet, i) => { 9 | const text = `${i} - ${pet}`; 10 | return
  • {text}
  • ; 11 | })} 12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/features/appRouting/assets/app/ssr-dynamic/optional-catch-all/[[...colors]]/page.jsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default function SSRDynamicOptionalCatchAllPage({ params }) { 4 | return ( 5 |
6 | {!params.colors ? ( 7 |

No color provided

8 | ) : ( 9 | <> 10 |

The provided colors are:

11 |
    12 | {params.colors.map((color, i) => ( 13 |
  • 14 | {i} - {color} 15 |
  • 16 | ))} 17 |
18 | 19 | )} 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /pages-e2e/features/appRouting/assets/app/ssr-dynamic/page/[pageName]/page.jsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default function SSRDynamicPageWithName({ params }) { 4 | return ( 5 |
6 |

This Page's name is: {params.pageName}

7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /pages-e2e/features/appRouting/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appRouting/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/appRouting/ssg-dynamic.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 3 | 4 | describe('ssg dynamic pages', () => { 5 | ['foo', 'bar', 'baz'].forEach(route => { 6 | const path = `/ssg-dynamic/${route}`; 7 | test(`visiting the statically generated ${path} page`, async () => { 8 | const page = await BROWSER.newPage(); 9 | const assertVisible = getAssertVisible(page); 10 | 11 | await page.goto(`${DEPLOYMENT_URL}${path}`); 12 | 13 | await assertVisible('h1', { 14 | hasText: 'SSGed Dynamic Page', 15 | }); 16 | 17 | await assertVisible('p', { 18 | hasText: `slug: ${route}`, 19 | }); 20 | }); 21 | }); 22 | 23 | ['foo/bar', 'non-existent'].forEach(route => { 24 | const path = `/ssg-dynamic/${route}`; 25 | test(`visiting an invalid / not statically generated ${path} page`, async () => { 26 | const page = await BROWSER.newPage(); 27 | const assertVisible = getAssertVisible(page); 28 | 29 | await page.goto(`${DEPLOYMENT_URL}${path}`); 30 | 31 | await assertVisible('h1', { 32 | hasText: '404', 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /pages-e2e/features/appRoutingSsrDynamicCatchAll/assets/app/ssr-dynamic-catch-all/catch-all/[...pets]/page.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default function SSRDynamicCatchAllPage({ params }) { 4 | return ( 5 |
6 |

The provided pets are:

7 |
    8 | {params.pets.map((pet, i) => { 9 | const text = `${i} - ${pet}`; 10 | return
  • {text}
  • ; 11 | })} 12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/features/appRoutingSsrDynamicCatchAll/assets/app/ssr-dynamic-catch-all/optional-catch-all/[[...colors]]/page.jsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default function SSRDynamicOptionalCatchAllPage({ params }) { 4 | return ( 5 |
6 | {!params.colors ? ( 7 |

No color provided

8 | ) : ( 9 | <> 10 |

The provided colors are:

11 |
    12 | {params.colors.map((color, i) => ( 13 |
  • 14 | {i} - {color} 15 |
  • 16 | ))} 17 |
18 | 19 | )} 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /pages-e2e/features/appRoutingSsrDynamicCatchAll/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appRoutingSsrDynamicCatchAll/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/appServerActions/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appServerActions/server-actions.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 3 | 4 | describe('server-actions', () => { 5 | test('simple forms using a local KV binding can read and write a value', async () => { 6 | const page = await BROWSER.newPage(); 7 | const assertVisible = getAssertVisible(page); 8 | 9 | await page.goto(`${DEPLOYMENT_URL}/server-actions/simple-kv-form`); 10 | 11 | await assertVisible('h1', { 12 | hasText: 'Server Actions - Simple KV Form', 13 | }); 14 | 15 | // let's always clear the value in case it is already set 16 | // (for example if this run is a retry run) 17 | const clearBtn = await assertVisible('button[data-test-id="clear-value"]'); 18 | await clearBtn.click(); 19 | 20 | await assertVisible('[data-test-id="kv-value-info"]', { 21 | hasText: 'No value is set for the key', 22 | }); 23 | 24 | const input = await assertVisible('[data-test-id="form-input"]'); 25 | await input.type('This is a test value!'); 26 | 27 | const submitBtn = await assertVisible('button[data-test-id="form-submit"]'); 28 | await submitBtn.click(); 29 | 30 | await assertVisible('[data-test-id="kv-value-info"]', { 31 | hasText: `The key's value is "This is a test value!"`, 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /pages-e2e/features/appServerActions/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/appStaticNotFoundWithEdgeLayout/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appStaticNotFoundWithEdgeLayout/setup.ts: -------------------------------------------------------------------------------- 1 | // no setup required 2 | -------------------------------------------------------------------------------- /pages-e2e/features/appWasm/assets/app/wasm/add-one/add-one.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/next-on-pages/3d84ab0096258febb88193c4b9d269ecbaf1b19b/pages-e2e/features/appWasm/assets/app/wasm/add-one/add-one.wasm -------------------------------------------------------------------------------- /pages-e2e/features/appWasm/assets/app/wasm/add-one/page.jsx: -------------------------------------------------------------------------------- 1 | import addOneWasm from './add-one.wasm?module'; 2 | 3 | export const runtime = 'edge'; 4 | 5 | export default async function WasmAddOnePage() { 6 | const addOneWasmModule = await WebAssembly.instantiate(addOneWasm); 7 | const addOne = addOneWasmModule.exports.add_one; 8 | 9 | const n = 4; 10 | const result = addOne(4); 11 | const message = `WASM says that ${n} + 1 = ${result}`; 12 | 13 | return ( 14 | <> 15 |

{message}

16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /pages-e2e/features/appWasm/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/appWasm/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/appWasm/wasm.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 3 | 4 | describe('App Wasm', () => { 5 | test(`wasm modules should work as expected`, async () => { 6 | const page = await BROWSER.newPage(); 7 | const assertVisible = getAssertVisible(page); 8 | 9 | await page.goto(`${DEPLOYMENT_URL}/wasm/add-one`); 10 | 11 | await assertVisible('h1', { 12 | hasText: 'WASM says that 4 + 1 = 5', 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /pages-e2e/features/customEntrypoint/assets/custom-entrypoint.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import nextOnPagesHandler from '@cloudflare/next-on-pages/fetch-handler'; 3 | 4 | export default { 5 | async fetch(...args) { 6 | const response = await nextOnPagesHandler.fetch(...args); 7 | 8 | response.headers.set('custom-entrypoint', '1'); 9 | 10 | return response; 11 | }, 12 | } as ExportedHandler<{ ASSETS: Fetcher }>; 13 | -------------------------------------------------------------------------------- /pages-e2e/features/customEntrypoint/custom-entrypoint.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | 3 | describe('Custom Entrypoint', () => { 4 | test('should set header on response in the worker entrypoint', async ({ 5 | expect, 6 | }) => { 7 | const response = await fetch(`${DEPLOYMENT_URL}/api/hello`); 8 | 9 | await expect(response.text()).resolves.toEqual('Hello world'); 10 | expect(response.headers.get('custom-entrypoint')).toEqual('1'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /pages-e2e/features/customEntrypoint/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/customEntrypoint/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/issue593/issue.test.ts: -------------------------------------------------------------------------------- 1 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 2 | import { describe, test } from 'vitest'; 3 | 4 | describe('issue-593', () => { 5 | test('navigating to /api/hello should return a Hello world response', async ({ 6 | expect, 7 | }) => { 8 | const response = await fetch(`${DEPLOYMENT_URL}/api/hello`); 9 | expect(await response.json()).toEqual({ 10 | text: 'Hello world!', 11 | }); 12 | }); 13 | 14 | test('/some-random-route should display the catch-all page', async () => { 15 | const page = await BROWSER.newPage(); 16 | const assertVisible = getAssertVisible(page); 17 | const pageUrl = `${DEPLOYMENT_URL}/some-random-route`; 18 | await page.goto(pageUrl); 19 | await assertVisible('h1', { hasText: 'catch-all route' }); 20 | }); 21 | 22 | test('/ should display the home page', async () => { 23 | const page = await BROWSER.newPage(); 24 | const assertVisible = getAssertVisible(page); 25 | const pageUrl = `${DEPLOYMENT_URL}/`; 26 | await page.goto(pageUrl); 27 | await assertVisible('h1', { hasText: 'home page' }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /pages-e2e/features/issue593/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/issue593/setup.ts: -------------------------------------------------------------------------------- 1 | // no setup required 2 | -------------------------------------------------------------------------------- /pages-e2e/features/issue696/issue.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | 3 | describe('issue-696', () => { 4 | describe('default trailing slashes redirection', () => { 5 | describe('fetching without an added trailing slash results in a redirected response', () => { 6 | test('the user gets the requested page', async ({ expect }) => { 7 | const response = await fetch(`${DEPLOYMENT_URL}/api/hello`); 8 | 9 | expect(response.redirected).toBe(true); 10 | expect(response.status).toBe(200); 11 | expect(response.url).toEqual(`${DEPLOYMENT_URL}/api/hello/`); 12 | }); 13 | 14 | test('the response is actually a redirect (and not a rewrite)', async ({ 15 | expect, 16 | }) => { 17 | const err = await fetch(`${DEPLOYMENT_URL}/api/hello`, { 18 | redirect: 'error', 19 | }) 20 | .then(() => null) 21 | .catch(e => e.cause.message); 22 | 23 | expect(err).toEqual('unexpected redirect'); 24 | }); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /pages-e2e/features/issue696/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/issue696/setup.ts: -------------------------------------------------------------------------------- 1 | // no setup required 2 | -------------------------------------------------------------------------------- /pages-e2e/features/issue797/issue.test.ts: -------------------------------------------------------------------------------- 1 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 2 | import { describe, expect, test } from 'vitest'; 3 | 4 | describe('issue-797', () => { 5 | test('should pass headers set in the middleware along to layouts and pages', async () => { 6 | const page = await BROWSER.newPage(); 7 | const assertVisible = getAssertVisible(page); 8 | const pageUrl = `${DEPLOYMENT_URL}/`; 9 | await page.goto(pageUrl); 10 | const html = await assertVisible('html'); 11 | 12 | const layoutAttrHeaderFromMiddleware = await html.getAttribute( 13 | 'layout-attr-header-from-middleware', 14 | ); 15 | 16 | expect(layoutAttrHeaderFromMiddleware).toEqual( 17 | 'this is a header set by the middleware!', 18 | ); 19 | 20 | const gottenReqHeaderP = await assertVisible( 21 | 'p[data-test-id="header-from-middleware"]', 22 | ); 23 | expect(await gottenReqHeaderP.innerText()).toEqual( 24 | 'header from middleware: this is a header set by the middleware!', 25 | ); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /pages-e2e/features/issue797/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/issue797/setup.ts: -------------------------------------------------------------------------------- 1 | // no setup required 2 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsI18n/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsI18n/setup.ts: -------------------------------------------------------------------------------- 1 | // No setup needed, this feature only implements tests specific to the pages14.0.0_i18n fixture 2 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/assets/pages/api/configs-headers/not-to-apply/some-route.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | runtime: 'experimental-edge', 3 | }; 4 | 5 | export default async function handler(req) { 6 | return new Response('api/configs-headers/not-to-apply/some-route route'); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/assets/pages/api/configs-headers/to-apply/some-route.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | runtime: 'experimental-edge', 3 | }; 4 | 5 | export default async function handler(req) { 6 | return new Response('api/configs-headers/to-apply/some-route route'); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/assets/pages/configs-rewrites/dynamic/[page].jsx: -------------------------------------------------------------------------------- 1 | export const config = { runtime: 'experimental-edge' }; 2 | 3 | export default function Page({ title }) { 4 | return

{title}

; 5 | } 6 | 7 | export async function getServerSideProps() { 8 | return { 9 | props: { title: 'This is a dynamic (configs-rewrites) page' }, 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/assets/pages/configs-rewrites/header-somewhere-else.jsx: -------------------------------------------------------------------------------- 1 | export const config = { runtime: 'experimental-edge' }; 2 | 3 | export default function Page({ title }) { 4 | return

{title}

; 5 | } 6 | 7 | export async function getServerSideProps() { 8 | return { 9 | props: { title: 'This is the "header-somewhere-else" page' }, 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/assets/pages/configs-rewrites/query-somewhere-else.jsx: -------------------------------------------------------------------------------- 1 | export const config = { runtime: 'experimental-edge' }; 2 | 3 | export default function Page({ title }) { 4 | return

{title}

; 5 | } 6 | 7 | export async function getServerSideProps() { 8 | return { 9 | props: { title: 'This is the "query-somewhere-else" page' }, 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/assets/pages/configs-rewrites/rewritten-wildcard/my-page.jsx: -------------------------------------------------------------------------------- 1 | export const config = { runtime: 'experimental-edge' }; 2 | 3 | export default function Page({ title }) { 4 | return

{title}

; 5 | } 6 | 7 | export async function getServerSideProps() { 8 | return { 9 | props: { title: 'This is the "rewritten-wildcard/my-page" page' }, 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/assets/pages/configs-rewrites/some-page.jsx: -------------------------------------------------------------------------------- 1 | export const config = { runtime: 'experimental-edge' }; 2 | 3 | export default function Page({ title }) { 4 | return ( 5 | <> 6 |

{title}

7 |

This page is static

8 | 9 | ); 10 | } 11 | 12 | export async function getServerSideProps() { 13 | return { 14 | props: { title: 'This is the "some-page" page' }, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/assets/pages/configs-rewrites/wildcard/my-page.jsx: -------------------------------------------------------------------------------- 1 | export const config = { runtime: 'experimental-edge' }; 2 | 3 | export default function Page({ title }) { 4 | return

{title}

; 5 | } 6 | 7 | export async function getServerSideProps() { 8 | return { 9 | props: { title: 'This is the "wildcard/my-page" page' }, 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/headers.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | 3 | describe('next.config.mjs Headers', () => { 4 | test('addition of headers to api response', async ({ expect }) => { 5 | const response = await fetch( 6 | `${DEPLOYMENT_URL}/api/configs-headers/to-apply/some-route`, 7 | ); 8 | expect(await response.text()).toBe( 9 | 'api/configs-headers/to-apply/some-route route', 10 | ); 11 | expect(response.headers.get('x-custom-configs-header')).toEqual( 12 | 'my custom header value (from next.config.mjs)', 13 | ); 14 | expect(response.headers.get('x-another-custom-configs-header')).toEqual( 15 | 'my other custom header value (from next.config.mjs)', 16 | ); 17 | }); 18 | 19 | test('no addition of headers to api response', async ({ expect }) => { 20 | const response = await fetch( 21 | `${DEPLOYMENT_URL}/api/configs-headers/not-to-apply/some-route`, 22 | ); 23 | expect(await response.text()).toBe( 24 | 'api/configs-headers/not-to-apply/some-route route', 25 | ); 26 | expect(response.headers.get('x-custom-configs-header')).toBe(null); 27 | expect(response.headers.get('x-another-custom-configs-header')).toBe(null); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/headers.ts: -------------------------------------------------------------------------------- 1 | export function headers() { 2 | return [ 3 | { 4 | source: '/api/configs-headers/to-apply/:path*', 5 | headers: [ 6 | { 7 | key: 'x-custom-configs-header', 8 | value: 'my custom header value (from next.config.mjs)', 9 | }, 10 | { 11 | key: 'x-another-custom-configs-header', 12 | value: 'my other custom header value (from next.config.mjs)', 13 | }, 14 | ], 15 | }, 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/redirects.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | 3 | describe('next.config.mjs Redirects', () => { 4 | test('basic non-permanent redirects', async ({ expect }) => { 5 | const response = await fetch( 6 | `${DEPLOYMENT_URL}/permanent-config-redirect`, 7 | { redirect: 'manual' }, 8 | ); 9 | expect(response.status).toBe(308); 10 | expect(response.headers.get('location')).toMatch( 11 | /\/permanent-config-redirect-destination$/, 12 | ); 13 | }); 14 | 15 | test('basic permanent redirects', async ({ expect }) => { 16 | const response = await fetch( 17 | `${DEPLOYMENT_URL}/non-permanent-config-redirect`, 18 | { redirect: 'manual' }, 19 | ); 20 | expect(response.status).toBe(307); 21 | expect(response.headers.get('location')).toMatch( 22 | /\/non-permanent-config-redirect-destination$/, 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/redirects.ts: -------------------------------------------------------------------------------- 1 | export function redirects() { 2 | return [ 3 | { 4 | source: '/permanent-config-redirect', 5 | destination: '/permanent-config-redirect-destination', 6 | permanent: true, 7 | }, 8 | { 9 | source: '/non-permanent-config-redirect', 10 | destination: '/non-permanent-config-redirect-destination', 11 | permanent: false, 12 | }, 13 | ]; 14 | } 15 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesConfigsRewritesRedirectsHeaders/rewrites.ts: -------------------------------------------------------------------------------- 1 | export function rewrites() { 2 | return { 3 | beforeFiles: [ 4 | { 5 | source: '/configs-rewrites/some-page', 6 | destination: '/configs-rewrites/query-somewhere-else', 7 | has: [{ type: 'query', key: 'overrideMe' }], 8 | }, 9 | { 10 | source: '/configs-rewrites/some-page', 11 | destination: '/configs-rewrites/header-somewhere-else', 12 | has: [{ type: 'header', key: 'overrideMe' }], 13 | }, 14 | { 15 | source: '/configs-rewrites/some-page', 16 | destination: '/configs-rewrites/header-somewhere-else', 17 | has: [{ type: 'header', key: 'overrideMe' }], 18 | }, 19 | { 20 | source: '/configs-rewrites/wildcard/:slug*', 21 | destination: '/configs-rewrites/rewritten-wildcard/:slug*', 22 | }, 23 | ], 24 | afterFiles: [ 25 | { 26 | source: '/configs-rewrites/some-page', 27 | destination: '/configs-rewrites/header-somewhere-else', 28 | }, 29 | { 30 | source: '/configs-rewrites/dynamic/:path*', 31 | destination: '/configs-rewrites/some-page', 32 | }, 33 | ], 34 | fallback: [ 35 | { 36 | source: '/configs-rewrites/:path*', 37 | destination: '/configs-rewrites/some-page', 38 | }, 39 | ], 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesGetRequestContext/assets/pages/api/get-request-context.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | runtime: 'experimental-edge', 3 | }; 4 | 5 | import { getRequestContext } from '@cloudflare/next-on-pages'; 6 | 7 | export default async function handler() { 8 | const { 9 | env: { MY_TOML_VAR: myTomlVar, MY_TOML_KV }, 10 | ctx, 11 | cf, 12 | } = getRequestContext(); 13 | 14 | await MY_TOML_KV.put('kv-key', 'kv-value'); 15 | const kvValue = await MY_TOML_KV.get('kv-key'); 16 | 17 | return Response.json({ 18 | myTomlVar, 19 | kvValue, 20 | typeofWaitUntil: typeof ctx.waitUntil, 21 | typeofCfColo: typeof cf.colo, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesGetRequestContext/assets/wrangler.toml: -------------------------------------------------------------------------------- 1 | [vars] 2 | MY_TOML_VAR = "my var from wrangler.toml" 3 | 4 | [[kv_namespaces]] 5 | binding = "MY_TOML_KV" 6 | id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 7 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesGetRequestContext/get-request-context.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | 3 | describe('getRequestContext', () => { 4 | // Note: we don't test `getRequestContext` in Pages components because those are 5 | // recognized as client components, thus they are unable to import `getRequestContext` 6 | // which is marked as server-only (not even if the import happens in server only 7 | // Pages code such as `getServerSideProps`!) 8 | 9 | test('works in api routes', async ({ expect }) => { 10 | const response = await fetch(`${DEPLOYMENT_URL}/api/get-request-context`); 11 | expect(await response.json()).toEqual({ 12 | kvValue: 'kv-value', 13 | myTomlVar: 'my var from wrangler.toml', 14 | typeofWaitUntil: 'function', 15 | typeofCfColo: 'string', 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesGetRequestContext/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesGetRequestContext/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesIssue578/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesIssue578/setup.ts: -------------------------------------------------------------------------------- 1 | // no setup required 2 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesMiddleware/assets/pages/api/middleware-test/hello.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | runtime: 'experimental-edge', 3 | }; 4 | 5 | export default async function handler(req) { 6 | return new Response('Hello middleware-test'); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesMiddleware/assets/pages/api/middleware-test/unreachable.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | runtime: 'experimental-edge', 3 | }; 4 | 5 | export default async function (req) { 6 | return new Response('ERROR: This route should not be reachable!'); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesMiddleware/assets/pages/middleware-test/pageA.jsx: -------------------------------------------------------------------------------- 1 | export const config = { runtime: 'experimental-edge' }; 2 | 3 | export async function getServerSideProps(context) { 4 | const requestHeaders = []; 5 | Object.entries(context.req?.headers ?? {}).forEach(([key, value]) => { 6 | requestHeaders.push({ key, value }); 7 | }); 8 | return { 9 | props: { requestHeaders }, 10 | }; 11 | } 12 | 13 | export default function Page({ requestHeaders }) { 14 | return ( 15 |
16 |

Page A

17 |

Headers

18 |

The request contained the following headers:

19 |
    20 | {requestHeaders.map(({ key, value }) => ( 21 |
  • 22 | {key}: {value} 23 |
  • 24 | ))} 25 |
26 | {process.env.frameworkVersion?.startsWith('12') && ( 27 |

Note: this application runs on Next.js v12

28 | )} 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesMiddleware/assets/pages/middleware-test/pageB.jsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export const config = { runtime: 'experimental-edge' }; 4 | 5 | export default function Page() { 6 | const links = [ 7 | { 8 | href: '/middleware-test/pageC?redirect-to-page-a', 9 | text: 'go to page A via middleware redirect', 10 | }, 11 | { 12 | href: '/middleware-test/pageC?rewrite-to-page-a', 13 | text: 'go to page C which is page A served via middleware rewrite', 14 | }, 15 | ]; 16 | 17 | return ( 18 |
19 |

Page B

20 | {links.map(({ href, text }) => ( 21 |
22 | {text} 23 |
24 | ))} 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesMiddleware/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesMiddleware/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/404.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 3 | 4 | describe('default not found pages', () => { 5 | ['/invalid-route', '/404', '/nested/non/existing/route'].map(path => 6 | test(`visiting ${path} results in the default 404 page`, async () => { 7 | const page = await BROWSER.newPage(); 8 | const assertVisible = getAssertVisible(page); 9 | 10 | await page.goto(`${DEPLOYMENT_URL}${path}`); 11 | 12 | await assertVisible('h1', { 13 | hasText: '404', 14 | }); 15 | 16 | await assertVisible('h2', { 17 | hasText: 'This page could not be found.', 18 | }); 19 | }), 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/500.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 3 | 4 | describe('default not found pages', () => { 5 | test(`visiting a page that throws results in the default 500 internal server error page`, async () => { 6 | const page = await BROWSER.newPage(); 7 | const assertVisible = getAssertVisible(page); 8 | 9 | await page.goto(`${DEPLOYMENT_URL}/500-error`); 10 | 11 | await assertVisible('h1', { 12 | hasText: '500', 13 | }); 14 | 15 | await assertVisible('h2', { 16 | hasText: 'Internal Server Error.', 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/assets/pages/500-error.jsx: -------------------------------------------------------------------------------- 1 | export const config = { runtime: 'experimental-edge' }; 2 | 3 | export async function getServerSideProps() { 4 | throw new Error('custom error'); 5 | 6 | return { 7 | props: { message: 'hello world' }, 8 | }; 9 | } 10 | 11 | export default function PageA({ message }) { 12 | return ( 13 |
14 |

Unaccessible page

15 |

this page should not be accessible since the component throws

16 |

{message}

17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/assets/pages/api/routing-trailing-slash-test.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | runtime: 'experimental-edge', 3 | }; 4 | 5 | export default async function handler(req) { 6 | return new Response('Hello world!'); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/assets/pages/ssg-dynamic/[slug].jsx: -------------------------------------------------------------------------------- 1 | export async function getStaticPaths() { 2 | const slugs = ['foo', 'bar', 'baz']; 3 | return { 4 | paths: slugs.map(slug => ({ 5 | params: { slug }, 6 | })), 7 | fallback: false, 8 | }; 9 | } 10 | 11 | export async function getStaticProps({ params: { slug } }) { 12 | return { props: { slug } }; 13 | } 14 | 15 | export default function Page({ slug }) { 16 | return ( 17 |
18 |

SSGed Dynamic Page

19 |

slug: {slug}

20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/assets/pages/ssr-dynamic/catch-all/[...pets].jsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | export const runtime = 'experimental-edge'; 4 | 5 | export default function SSRDynamicCatchAllPage() { 6 | const router = useRouter(); 7 | 8 | return ( 9 |
10 |

The provided pets are:

11 |
    12 | {router.query.pets?.map((pet, i) => ( 13 |
  • 14 | {i} - {pet} 15 |
  • 16 | ))} 17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/assets/pages/ssr-dynamic/optional-catch-all/[[...colors]].jsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | export const runtime = 'experimental-edge'; 4 | 5 | export default function SSRDynamicCatchAllPage() { 6 | const router = useRouter(); 7 | 8 | return ( 9 |
10 | {!router.query.colors ? ( 11 |

No color provided

12 | ) : ( 13 | <> 14 |

The provided colors are:

15 |
    16 | {router.query.colors.map((color, i) => ( 17 |
  • 18 | {i} - {color} 19 |
  • 20 | ))} 21 |
22 | 23 | )} 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/assets/pages/ssr-dynamic/page/[pageName].tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | export const runtime = 'experimental-edge'; 4 | 5 | export default function SSRDynamicPageWithName() { 6 | const router = useRouter(); 7 | return ( 8 |
9 |

This Page's name is: {router.query.pageName}

10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRouting/trailingSlash.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | 3 | describe('default trailing slashes redirection', () => { 4 | describe('fetching with an added trailing slashed results in a redirected response', () => { 5 | test('the user gets the requested page', async ({ expect }) => { 6 | const response = await fetch( 7 | `${DEPLOYMENT_URL}/api/routing-trailing-slash-test/`, 8 | ); 9 | 10 | expect(response.redirected).toBe(true); 11 | expect(response.status).toBe(200); 12 | expect(response.url).toEqual( 13 | `${DEPLOYMENT_URL}/api/routing-trailing-slash-test`, 14 | ); 15 | }); 16 | 17 | test('the response is actually a redirect (and not a rewrite)', async ({ 18 | expect, 19 | }) => { 20 | const err = await fetch( 21 | `${DEPLOYMENT_URL}/api/routing-trailing-slash-test/`, 22 | { redirect: 'error' }, 23 | ) 24 | .then(() => null) 25 | .catch(e => e.cause.message); 26 | 27 | expect(err).toEqual('unexpected redirect'); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRoutingSsrDynamicCatchAll/assets/pages/ssr-dynamic-catch-all/catch-all/[...pets].jsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | export const runtime = 'experimental-edge'; 4 | 5 | export default function SSRDynamicCatchAllPage() { 6 | const router = useRouter(); 7 | 8 | return ( 9 |
10 |

The provided pets are:

11 |
    12 | {router.query.pets?.map((pet, i) => ( 13 |
  • 14 | {i} - {pet} 15 |
  • 16 | ))} 17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRoutingSsrDynamicCatchAll/assets/pages/ssr-dynamic-catch-all/optional-catch-all/[[...colors]].jsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | export const runtime = 'experimental-edge'; 4 | 5 | export default function SSRDynamicCatchAllPage() { 6 | const router = useRouter(); 7 | 8 | return ( 9 |
10 | {!router.query.colors ? ( 11 |

No color provided

12 | ) : ( 13 | <> 14 |

The provided colors are:

15 |
    16 | {router.query.colors.map((color, i) => ( 17 |
  • 18 | {i} - {color} 19 |
  • 20 | ))} 21 |
22 | 23 | )} 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRoutingSsrDynamicCatchAll/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesRoutingSsrDynamicCatchAll/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesWasm/assets/pages/wasm/add-one/add-one.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/next-on-pages/3d84ab0096258febb88193c4b9d269ecbaf1b19b/pages-e2e/features/pagesWasm/assets/pages/wasm/add-one/add-one.wasm -------------------------------------------------------------------------------- /pages-e2e/features/pagesWasm/assets/pages/wasm/add-one/index.jsx: -------------------------------------------------------------------------------- 1 | import addOneWasm from './add-one.wasm?module'; 2 | 3 | export const config = { runtime: 'experimental-edge' }; 4 | 5 | export async function getServerSideProps() { 6 | const addOneWasmModule = await WebAssembly.instantiate(addOneWasm); 7 | const addOne = addOneWasmModule.exports.add_one; 8 | 9 | const n = 4; 10 | const result = addOne(4); 11 | const message = `WASM says that ${n} + 1 = ${result}`; 12 | 13 | return { 14 | props: { message }, 15 | }; 16 | } 17 | 18 | export default function WasmAddOnePage({ message }) { 19 | return ( 20 | <> 21 |

{message}

22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesWasm/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesWasm/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/pagesWasm/wasm.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { getAssertVisible } from '@features-utils/getAssertVisible'; 3 | 4 | describe('Pages Wasm', () => { 5 | test(`wasm modules should work as expected`, async () => { 6 | const page = await BROWSER.newPage(); 7 | const assertVisible = getAssertVisible(page); 8 | 9 | await page.goto(`${DEPLOYMENT_URL}/wasm/add-one`); 10 | 11 | await assertVisible('h1', { 12 | hasText: 'WASM says that 4 + 1 = 5', 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /pages-e2e/features/setup-types.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | namespace NodeJS { 3 | interface ProcessEnv { 4 | WORKSPACE_DIR: string; 5 | } 6 | } 7 | } 8 | 9 | export {}; 10 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppApiRoutes/api-routes.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest'; 2 | 3 | describe('Simple Pages API Routes', () => { 4 | it('should return a Hello world response', async ({ expect }) => { 5 | const response = await fetch(`${DEPLOYMENT_URL}/api/hello`); 6 | expect(await response.text()).toBe('Hello world'); 7 | }); 8 | 9 | it("should correctly read the request's headers", async ({ expect }) => { 10 | const response = await fetch(`${DEPLOYMENT_URL}/api/headers`, { 11 | headers: { 12 | myHeader: 'my-header-value', 13 | Cookie: 'cookieTestName=cookieTestValue', 14 | }, 15 | }); 16 | 17 | const respJson = await response.json(); 18 | 19 | const myHeader = ( 20 | respJson.headers as { name: string; value: string }[] 21 | ).find(({ name }) => name.toLowerCase() === 'myheader'); 22 | 23 | expect(myHeader.value).toBe('my-header-value'); 24 | expect(respJson.cookies).toEqual([ 25 | { name: 'cookieTestName', value: 'cookieTestValue' }, 26 | ]); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppApiRoutes/assets/app/api/headers/route.js: -------------------------------------------------------------------------------- 1 | import { cookies, headers } from 'next/headers'; 2 | 3 | export const runtime = 'edge'; 4 | 5 | export async function GET() { 6 | const allCookies = []; 7 | cookies() 8 | .getAll() 9 | .forEach(({ name, value }) => { 10 | allCookies.push({ name, value }); 11 | }); 12 | 13 | const allHeaders = []; 14 | headers().forEach((value, name) => { 15 | allHeaders.push({ name, value }); 16 | }); 17 | 18 | return new Response( 19 | JSON.stringify({ 20 | cookies: allCookies, 21 | headers: allHeaders, 22 | }), 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppApiRoutes/assets/app/api/hello/route.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export async function GET(request) { 4 | return new Response('Hello world'); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppApiRoutes/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppApiRoutes/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppSsrRoutes/assets/app/routeA/page.jsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default async function RouteA() { 4 | const message = await getServerSideMessage(); 5 | 6 | return ( 7 | <> 8 |

{message}

9 | 10 | ); 11 | } 12 | 13 | async function getServerSideMessage() { 14 | return 'This route was Server Side Rendered'; 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppSsrRoutes/assets/app/ssr-navigation/layout.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default async function Layout({ children }) { 4 | const title = await new Promise(resolve => 5 | resolve('Server Side Rendered Navigation'), 6 | ); 7 | 8 | return ( 9 | <> 10 |
11 |

{title}

12 | to index 13 | to page A 14 | to page B 15 |
16 |
17 |
{children}
18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppSsrRoutes/assets/app/ssr-navigation/page.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default async function IndexPageA() { 4 | const title = await new Promise(resolve => 5 | resolve('Server Side Rendered Index'), 6 | ); 7 | return

{title}

; 8 | } 9 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppSsrRoutes/assets/app/ssr-navigation/pageA/page.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default async function PageA() { 4 | const title = await new Promise(resolve => 5 | resolve('Server Side Rendered Page A'), 6 | ); 7 | return

{title}

; 8 | } 9 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppSsrRoutes/assets/app/ssr-navigation/pageB/page.js: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default async function PageB() { 4 | const title = await new Promise(resolve => 5 | resolve('Server Side Rendered Page B'), 6 | ); 7 | return

{title}

; 8 | } 9 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppSsrRoutes/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppSsrRoutes/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppSsrRoutes/ssr-routes.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest'; 2 | 3 | describe('Simple App SSR Routes', () => { 4 | it('should return a page with a server-side rendered message', async ({ 5 | expect, 6 | }) => { 7 | const page = await BROWSER.newPage(); 8 | await page.goto(`${DEPLOYMENT_URL}/routeA`); 9 | 10 | const h1Text = await page.locator('h1').textContent(); 11 | expect(h1Text).toContain('This route was Server Side Rendered'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppStaticRoutesAndAssets/assets.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest'; 2 | 3 | describe('Simple App Assets', () => { 4 | it('should return the favicon', async ({ expect }) => { 5 | const response = await fetch(`${DEPLOYMENT_URL}/favicon.ico`); 6 | 7 | expect(response.status).toBe(200); 8 | expect(response.headers.get('Content-Type')).toMatch(/image\/*/); 9 | }); 10 | 11 | it('should return the Vercel logo svg', async ({ expect }) => { 12 | const response = await fetch(`${DEPLOYMENT_URL}/vercel.svg`); 13 | 14 | expect(response.status).toBe(200); 15 | expect(response.headers.get('Content-Type')).toBe('image/svg+xml'); 16 | expect(await response.text()).toContain('svg'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppStaticRoutesAndAssets/assets/app/navigation/layout.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function Layout({ children }) { 4 | return ( 5 | <> 6 |
7 |

Navigation

8 | to index 9 | to page A 10 | to page B 11 |
12 |
13 |
{children}
14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppStaticRoutesAndAssets/assets/app/navigation/page.js: -------------------------------------------------------------------------------- 1 | export default function IndexPage() { 2 | return

Index

; 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppStaticRoutesAndAssets/assets/app/navigation/pageA/page.js: -------------------------------------------------------------------------------- 1 | export default function PageA() { 2 | return

Page A

; 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppStaticRoutesAndAssets/assets/app/navigation/pageB/page.js: -------------------------------------------------------------------------------- 1 | export default function PageB() { 2 | return

Page B

; 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppStaticRoutesAndAssets/assets/app/staticRouteA/page.jsx: -------------------------------------------------------------------------------- 1 | export default async function StaticRouteA() { 2 | return ( 3 | <> 4 |

This is a static route

5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppStaticRoutesAndAssets/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppStaticRoutesAndAssets/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/simpleAppStaticRoutesAndAssets/static-routes.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest'; 2 | 3 | describe('Simple App Static Routes', () => { 4 | it('should view the Vercel Logo present in the default static index page', async ({ 5 | expect, 6 | }) => { 7 | const page = await BROWSER.newPage(); 8 | await page.goto(`${DEPLOYMENT_URL}`); 9 | 10 | const nextLogoIsVisible = await page 11 | .locator('img[alt="Vercel Logo"]') 12 | .isVisible(); 13 | expect(nextLogoIsVisible).toBe(true); 14 | }); 15 | 16 | it('should apply the correct css styling to the static index page', async ({ 17 | expect, 18 | }) => { 19 | const page = await BROWSER.newPage(); 20 | await page.goto(`${DEPLOYMENT_URL}`); 21 | 22 | const bodyElement = await page.waitForSelector('body'); 23 | const bodyBackground = await bodyElement.evaluate(el => { 24 | return window.getComputedStyle(el).getPropertyValue('background'); 25 | }); 26 | expect(bodyBackground).toContain('linear-gradient'); 27 | }); 28 | 29 | it('should return a user defined static page', async ({ expect }) => { 30 | const page = await BROWSER.newPage(); 31 | await page.goto(`${DEPLOYMENT_URL}/staticRouteA`); 32 | 33 | const h1Text = await page.locator('h1').textContent(); 34 | expect(h1Text).toContain('This is a static route'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesApiRoutes/api-routes.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest'; 2 | 3 | describe('Simple App API Routes', () => { 4 | it('should return Hello world response', async ({ expect }) => { 5 | const response = await fetch(`${DEPLOYMENT_URL}/api/hello`); 6 | expect(await response.text()).toBe('Hello world'); 7 | }); 8 | 9 | it("should correctly read the request's headers", async ({ expect }) => { 10 | const response = await fetch(`${DEPLOYMENT_URL}/api/headers`, { 11 | headers: { 12 | myHeader: 'my-header-value', 13 | Cookie: 'cookieTestName=cookieTestValue', 14 | }, 15 | }); 16 | 17 | const respJson = await response.json(); 18 | 19 | const myHeader = ( 20 | respJson.headers as { name: string; value: string }[] 21 | ).find(({ name }) => name.toLowerCase() === 'myheader'); 22 | 23 | expect(myHeader.value).toBe('my-header-value'); 24 | expect(respJson.cookies).toEqual([ 25 | { name: 'cookieTestName', value: 'cookieTestValue' }, 26 | ]); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesApiRoutes/assets/pages/api/headers.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | runtime: 'experimental-edge', 3 | }; 4 | 5 | export default function handler(req) { 6 | const allCookies = []; 7 | 8 | if (typeof req.cookies.getAll !== 'function') { 9 | // Next.js <= 12 10 | Array.from(req.cookies.keys()).forEach(name => { 11 | const value = req.cookies.get(name); 12 | allCookies.push({ name, value }); 13 | }); 14 | } else { 15 | // Next.js >= 13 16 | req.cookies.getAll().forEach(({ value, name }) => { 17 | allCookies.push({ name, value }); 18 | }); 19 | } 20 | 21 | const allHeaders = []; 22 | req.headers.forEach((value, name) => { 23 | allHeaders.push({ name, value }); 24 | }); 25 | 26 | return new Response( 27 | JSON.stringify({ 28 | cookies: allCookies, 29 | headers: allHeaders, 30 | }), 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesApiRoutes/assets/pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js Edge API Routes: https://nextjs.org/docs/api-routes/edge-api-routes 2 | 3 | export const config = { 4 | runtime: 'experimental-edge', 5 | }; 6 | 7 | export default async function handler(req) { 8 | return new Response('Hello world'); 9 | } 10 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesApiRoutes/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesApiRoutes/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesSsrRoutes/assets/pages/routeA.jsx: -------------------------------------------------------------------------------- 1 | export const config = { runtime: 'experimental-edge' }; 2 | 3 | export async function getServerSideProps() { 4 | return { 5 | props: { message: 'This route was Server Side Rendered' }, 6 | }; 7 | } 8 | 9 | export default function SsrRouteA({ message }) { 10 | return ( 11 | <> 12 |

{message}

13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesSsrRoutes/assets/pages/ssr-navigation/index.js: -------------------------------------------------------------------------------- 1 | import Layout from './layout'; 2 | 3 | export const config = { runtime: 'experimental-edge' }; 4 | 5 | export async function getServerSideProps() { 6 | return { 7 | props: { title: 'Server Side Rendered Index' }, 8 | }; 9 | } 10 | 11 | export default function IndexPage({ title }) { 12 | return ( 13 | 14 |

{title}

15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesSsrRoutes/assets/pages/ssr-navigation/layout.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function Layout({ children }) { 4 | return ( 5 | <> 6 |
7 |

Navigation

8 | to index 9 | to page A 10 | to page B 11 |
12 |
13 |
{children}
14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesSsrRoutes/assets/pages/ssr-navigation/pageA.js: -------------------------------------------------------------------------------- 1 | import Layout from './layout'; 2 | 3 | export const config = { runtime: 'experimental-edge' }; 4 | 5 | export async function getServerSideProps() { 6 | return { 7 | props: { title: 'Server Side Rendered Page A' }, 8 | }; 9 | } 10 | 11 | export default function PageA({ title }) { 12 | return ( 13 | 14 |

{title}

15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesSsrRoutes/assets/pages/ssr-navigation/pageB.js: -------------------------------------------------------------------------------- 1 | import Layout from './layout'; 2 | 3 | export const config = { runtime: 'experimental-edge' }; 4 | 5 | export async function getServerSideProps() { 6 | return { 7 | props: { title: 'Server Side Rendered Page B' }, 8 | }; 9 | } 10 | 11 | export default function PageB({ title }) { 12 | return ( 13 | 14 |

{title}

15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesSsrRoutes/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesSsrRoutes/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesSsrRoutes/ssr-routes.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest'; 2 | 3 | describe('Simple Pages SSR Routes', () => { 4 | it('should return a page with a server-side rendered message', async ({ 5 | expect, 6 | }) => { 7 | const page = await BROWSER.newPage(); 8 | await page.goto(`${DEPLOYMENT_URL}/routeA`); 9 | 10 | const h1Text = await page.locator('h1').textContent(); 11 | expect(h1Text).toContain('This route was Server Side Rendered'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesStaticRoutesAndAssets/assets.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest'; 2 | 3 | describe('Simple Pages Assets', () => { 4 | it('should return the favicon', async ({ expect }) => { 5 | const response = await fetch(`${DEPLOYMENT_URL}/favicon.ico`); 6 | 7 | expect(response.status).toBe(200); 8 | expect(response.headers.get('Content-Type')).toMatch(/image\/*/); 9 | }); 10 | 11 | it('should return the Vercel logo svg', async ({ expect }) => { 12 | const response = await fetch(`${DEPLOYMENT_URL}/vercel.svg`); 13 | 14 | expect(response.status).toBe(200); 15 | expect(response.headers.get('Content-Type')).toBe('image/svg+xml'); 16 | expect(await response.text()).toContain('svg'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesStaticRoutesAndAssets/assets/pages/navigation/index.js: -------------------------------------------------------------------------------- 1 | import Layout from './layout'; 2 | 3 | export default function IndexPage() { 4 | return ( 5 | 6 |

Index

7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesStaticRoutesAndAssets/assets/pages/navigation/layout.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function Layout({ children }) { 4 | return ( 5 | <> 6 |
7 |

Navigation

8 | to index 9 | to page A 10 | to page B 11 |
12 |
13 |
{children}
14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesStaticRoutesAndAssets/assets/pages/navigation/pageA.js: -------------------------------------------------------------------------------- 1 | import Layout from './layout'; 2 | 3 | export default function PageA() { 4 | return ( 5 | 6 |

Page A

7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesStaticRoutesAndAssets/assets/pages/navigation/pageB.js: -------------------------------------------------------------------------------- 1 | import Layout from './layout'; 2 | 3 | export default function PageB() { 4 | return ( 5 | 6 |

Page B

7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesStaticRoutesAndAssets/assets/pages/staticRouteA.jsx: -------------------------------------------------------------------------------- 1 | export default function StaticRouteA() { 2 | return ( 3 | <> 4 |

This is a static route

5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesStaticRoutesAndAssets/main.feature: -------------------------------------------------------------------------------- 1 | { 2 | "setup": "node --loader tsm setup.ts" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/features/simplePagesStaticRoutesAndAssets/setup.ts: -------------------------------------------------------------------------------- 1 | import { copyWorkspaceAssets } from '../_utils/copyWorkspaceAssets'; 2 | await copyWorkspaceAssets(); 3 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app13.4.0/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | .vscode 38 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app13.4.0/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/next-on-pages/3d84ab0096258febb88193c4b9d269ecbaf1b19b/pages-e2e/fixtures/app13.4.0/app/favicon.ico -------------------------------------------------------------------------------- /pages-e2e/fixtures/app13.4.0/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | 3 | export const metadata = { 4 | title: 'Create Next App', 5 | description: 'Generated by create next app', 6 | }; 7 | 8 | export default function RootLayout({ 9 | children, 10 | }: { 11 | children: React.ReactNode; 12 | }) { 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app13.4.0/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "simpleAppApiRoutes", 4 | "simpleAppSsrRoutes", 5 | "simpleAppStaticRoutesAndAssets", 6 | "appMiddleware", 7 | "appRouting", 8 | "appConfigsTrailingSlashFalse", 9 | "appConfigsRewritesRedirectsHeaders", 10 | "appWasm", 11 | "appGetRequestContext" 12 | ], 13 | "localSetup": "npm install", 14 | "buildConfig": { 15 | "buildCommand": "npx --force ../../../packages/next-on-pages", 16 | "buildOutputDirectory": ".vercel/output/static" 17 | }, 18 | "deploymentConfig": { 19 | "compatibilityFlags": ["nodejs_compat"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app13.4.0/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | appDir: true, 5 | }, 6 | env: { 7 | frameworkVersion: '13.4.0', 8 | }, 9 | }; 10 | 11 | export default nextConfig; 12 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app13.4.0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app13.4.0", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@cloudflare/next-on-pages": "../../../packages/next-on-pages", 13 | "@types/node": "20.4.0", 14 | "@types/react": "18.2.14", 15 | "@types/react-dom": "18.2.6", 16 | "next": "13.5.4", 17 | "react": "18.2.0", 18 | "react-dom": "18.2.0", 19 | "typescript": "5.1.6" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app13.4.0/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app13.4.0/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app14.0.0/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app14.0.0/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/next-on-pages/3d84ab0096258febb88193c4b9d269ecbaf1b19b/pages-e2e/fixtures/app14.0.0/app/favicon.ico -------------------------------------------------------------------------------- /pages-e2e/fixtures/app14.0.0/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | import './globals.css'; 3 | 4 | export const metadata: Metadata = { 5 | title: 'Create Next App', 6 | description: 'Generated by create next app', 7 | }; 8 | 9 | export default function RootLayout({ 10 | children, 11 | }: { 12 | children: React.ReactNode; 13 | }) { 14 | return ( 15 | 16 | {children} 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app14.0.0/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "simpleAppApiRoutes", 4 | "appRouting", 5 | "appConfigsTrailingSlashTrue", 6 | "appWasm", 7 | "appGetRequestContext" 8 | ], 9 | "localSetup": "npm install", 10 | "buildConfig": { 11 | "buildCommand": "npx --force ../../../packages/next-on-pages", 12 | "buildOutputDirectory": ".vercel/output/static" 13 | }, 14 | "deploymentConfig": { 15 | "compatibilityFlags": ["nodejs_compat"], 16 | "kvNamespaces": { 17 | "MY_KV": { 18 | "production": {"id": "00000000000000000000000000000000"}, 19 | "staging": {"id": "00000000000000000000000000000000"} 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app14.0.0/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app14.0.0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app14.0.0", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "14.0.0", 13 | "react": "^18", 14 | "react-dom": "^18" 15 | }, 16 | "devDependencies": { 17 | "@cloudflare/next-on-pages": "../../../packages/next-on-pages", 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "typescript": "^5", 22 | "vercel": "^32.6.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app14.0.0/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/app14.0.0/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appLatest/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "simpleAppApiRoutes", 4 | "simpleAppSsrRoutes", 5 | "simpleAppStaticRoutesAndAssets", 6 | "appMiddleware", 7 | "appRouting", 8 | "appRoutingSsrDynamicCatchAll", 9 | "appConfigsTrailingSlashFalse", 10 | "appConfigsRewritesRedirectsHeaders", 11 | "appWasm", 12 | "appServerActions", 13 | "appGetRequestContext", 14 | "customEntrypoint" 15 | ], 16 | "localSetup": "./setup.sh", 17 | "buildConfig": { 18 | "buildCommand": "npx --force ../../../packages/next-on-pages --custom-entrypoint=./custom-entrypoint.ts", 19 | "buildOutputDirectory": ".vercel/output/static" 20 | }, 21 | "deploymentConfig": { 22 | "compatibilityFlags": ["nodejs_compat"], 23 | "kvNamespaces": { 24 | "MY_KV": { 25 | "production": { "id": "00000000000000000000000000000000" }, 26 | "staging": { "id": "00000000000000000000000000000000" } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appLatest/setup.sh: -------------------------------------------------------------------------------- 1 | # Create a package.json in the workspace directory (this is needed because otherwise npm creates 2 | # the next app at the top of the pages-e2es dir instead of doing that in the current directory) 3 | # 4 | # Note: this seems to be a weird node 16 behavior it doesn't seem to happen in 18, so this can be 5 | # removed when we move to node 18 6 | echo "{}" > package.json 7 | 8 | # Create an app application in the application sub-directory (since the next cli complains if the target directory is not empty) 9 | npx create-next-app@latest application appLatest --ts --no-eslint --no-tailwind --no-src-dir --app --import-alias '@/*' 10 | 11 | # Move everything back in the current directory (so that we follow the same structure as the other fixtures 12 | # and don't break copyWorkspaceAssets) 13 | cp -a application/. . 14 | 15 | # Delete the no longer needed application directory 16 | rm -rf application 17 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appLatestNoNodeJsCompat/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "appNoNodeJsCompatFlag" 4 | ], 5 | "localSetup": "./setup.sh", 6 | "buildConfig": { 7 | "buildCommand": "npx --force ../../../packages/next-on-pages", 8 | "buildOutputDirectory": ".vercel/output/static" 9 | }, 10 | "deploymentConfig": { 11 | "compatibilityFlags": [] //note: no "nodejs_compat" included 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appLatestNoNodeJsCompat/setup.sh: -------------------------------------------------------------------------------- 1 | # Create a package.json in the workspace directory (this is needed because otherwise npm creates 2 | # the next app at the top of the pages-e2es dir instead of doing that in the current directory) 3 | # 4 | # Note: this seems to be a weird node 16 behavior it doesn't seem to happen in 18, so this can be 5 | # removed when we move to node 18 6 | echo "{}" > package.json 7 | 8 | # Create an app application in the application sub-directory (since the next cli complains if the target directory is not empty) 9 | npx create-next-app@latest application appLatest --ts --no-eslint --no-tailwind --no-src-dir --app --import-alias '@/*' 10 | 11 | # Move everything back in the current directory (so that we follow the same structure as the other fixtures 12 | # and don't break copyWorkspaceAssets) 13 | cp -a application/. . 14 | 15 | # Delete the no longer needed application directory 16 | rm -rf application 17 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/README.md: -------------------------------------------------------------------------------- 1 | This application relates to: https://github.com/cloudflare/next-on-pages/issues/413 2 | 3 | It comprises of: 4 | 5 | - a simple home page 6 | - a custom static not-found route 7 | - a layout containing server-side logic 8 | 9 | the vercel build process in such case creates an invalid (and unnecessary) \_error.func 10 | lambda which if not ignored with would cause the build to fail 11 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { headers } from 'next/headers'; 2 | 3 | export const runtime = 'edge'; 4 | 5 | export default async function RootLayout({ 6 | children, 7 | }: { 8 | children: React.ReactNode; 9 | }) { 10 | const serverSideInfo = await getServerSideInfo(); 11 | return ( 12 | 13 | 16 |
17 | 24 | {serverSideInfo} 25 | 26 |
34 | {children} 35 |
36 |
37 | 38 | 39 | ); 40 | } 41 | 42 | async function getServerSideInfo() { 43 | // Note: headers can only be called on the server 44 | const headersList = headers(); 45 | const acceptHeader = headersList.get('accept'); 46 | await new Promise(resolve => setTimeout(resolve, 200)); 47 | return `[server side info] the request's accept header value is: "${acceptHeader}"`; 48 | } 49 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function NotFound() { 4 | return ( 5 |
6 |

Not Found

7 |

The requested page could not be found

8 | Return Home 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/app/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return ( 3 |
4 |

Home Page

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "appStaticNotFoundWithEdgeLayout" 4 | ], 5 | "localSetup": "./setup.sh", 6 | "buildConfig": { 7 | "buildCommand": "npx --force ../../../packages/next-on-pages", 8 | "buildOutputDirectory": ".vercel/output/static" 9 | }, 10 | "deploymentConfig": { 11 | "compatibilityFlags": ["nodejs_compat"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "not-found-nop-test", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "pages:build": "npx @cloudflare/next-on-pages -s", 11 | "pages:deploy": "npm run pages:build && wrangler pages deploy .vercel/output/static", 12 | "pages:watch": "npx @cloudflare/next-on-pages --watch", 13 | "pages:dev": "npx wrangler pages dev .vercel/output/static --compatibility-flag=nodejs_compat" 14 | }, 15 | "dependencies": { 16 | "@types/node": "20.4.5", 17 | "@types/react": "18.2.18", 18 | "@types/react-dom": "18.2.7", 19 | "next": "14.0.0", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0", 22 | "typescript": "5.1.6" 23 | }, 24 | "devDependencies": { 25 | "vercel": "^32.4.1", 26 | "wrangler": "^3.19.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/setup.sh: -------------------------------------------------------------------------------- 1 | # No setup required -------------------------------------------------------------------------------- /pages-e2e/fixtures/appStaticNotFoundWithEdgeLayout/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593App/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593App/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "issue593" 4 | ], 5 | "buildConfig": { 6 | "buildCommand": "npx --force ../../../packages/next-on-pages", 7 | "buildOutputDirectory": ".vercel/output/static" 8 | }, 9 | "deploymentConfig": { 10 | "compatibilityFlags": ["nodejs_compat"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593App/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | trailingSlash: true, 4 | reactStrictMode: true, 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593App/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamic-routes-api-404", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "pages:build": "npx @cloudflare/next-on-pages -m", 11 | "pages:deploy": "npm run pages:build && wrangler pages deploy .vercel/output/static", 12 | "pages:watch": "npx @cloudflare/next-on-pages --watch", 13 | "pages:dev": "npx wrangler pages dev .vercel/output/static --compatibility-date=2023-11-21 --compatibility-flag=nodejs_compat" 14 | }, 15 | "dependencies": { 16 | "next": "14.0.4", 17 | "react": "^18", 18 | "react-dom": "^18" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^20", 22 | "@types/react": "^18", 23 | "@types/react-dom": "^18", 24 | "eslint": "^8", 25 | "eslint-config-next": "14.0.4", 26 | "typescript": "^5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593App/src/app/[...slug]/page.tsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default function Page() { 4 | return

catch-all route

; 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593App/src/app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export async function GET(req: Request) { 4 | return Response.json({ text: 'Hello world!' }); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593App/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function RootLayout({ 2 | children, 3 | }: { 4 | children: React.ReactNode; 5 | }) { 6 | return ( 7 | 8 | {children} 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593App/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return ( 3 |
4 |

home page

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593App/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "paths": { 17 | "@/*": ["./src/*"] 18 | }, 19 | "plugins": [ 20 | { 21 | "name": "next" 22 | } 23 | ] 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "issue593" 4 | ], 5 | "buildConfig": { 6 | "buildCommand": "npx --force ../../../packages/next-on-pages", 7 | "buildOutputDirectory": ".vercel/output/static" 8 | }, 9 | "deploymentConfig": { 10 | "compatibilityFlags": ["nodejs_compat"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | trailingSlash: true, 4 | reactStrictMode: true, 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamic-routes-api-404", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "pages:build": "npx @cloudflare/next-on-pages -m", 11 | "pages:deploy": "npm run pages:build && wrangler pages deploy .vercel/output/static", 12 | "pages:watch": "npx @cloudflare/next-on-pages --watch", 13 | "pages:dev": "npx wrangler pages dev .vercel/output/static --compatibility-date=2023-11-21 --compatibility-flag=nodejs_compat" 14 | }, 15 | "dependencies": { 16 | "next": "14.0.4", 17 | "react": "^18", 18 | "react-dom": "^18" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^20", 22 | "@types/react": "^18", 23 | "@types/react-dom": "^18", 24 | "eslint": "^8", 25 | "eslint-config-next": "14.0.4", 26 | "typescript": "^5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/src/pages/[...slug].tsx: -------------------------------------------------------------------------------- 1 | export const runtime = 'experimental-edge'; 2 | 3 | export default function Page() { 4 | return

catch-all route

; 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from 'next/app'; 2 | 3 | export default function App({ Component, pageProps }: AppProps) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document'; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/src/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default function handler(req: Request) { 4 | return Response.json({ text: 'Hello world!' }); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return ( 3 |
4 |

home page

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue593Pages/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "paths": { 17 | "@/*": ["./src/*"] 18 | } 19 | }, 20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696App/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696App/app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export async function GET(req: Request) { 4 | return Response.json({ text: 'Hello world!' }); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696App/app/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function RootLayout({ 2 | children, 3 | }: { 4 | children: React.ReactNode; 5 | }) { 6 | return ( 7 | 8 | {children} 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696App/app/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return ( 3 |
4 |

home page

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696App/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "issue696" 4 | ], 5 | "buildConfig": { 6 | "buildCommand": "npx --force ../../../packages/next-on-pages", 7 | "buildOutputDirectory": ".vercel/output/static" 8 | }, 9 | "deploymentConfig": { 10 | "compatibilityFlags": ["nodejs_compat"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696App/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from 'next/server'; 2 | 3 | export async function middleware(req: NextRequest) {} 4 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696App/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | trailingSlash: true, 4 | reactStrictMode: true, 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696App/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamic-routes-api-404", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "pages:build": "npx @cloudflare/next-on-pages -m", 11 | "pages:deploy": "npm run pages:build && wrangler pages deploy .vercel/output/static", 12 | "pages:watch": "npx @cloudflare/next-on-pages --watch", 13 | "pages:dev": "npx wrangler pages dev .vercel/output/static --compatibility-date=2023-11-21 --compatibility-flag=nodejs_compat" 14 | }, 15 | "dependencies": { 16 | "next": "14.0.4", 17 | "react": "^18", 18 | "react-dom": "^18" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^20", 22 | "@types/react": "^18", 23 | "@types/react-dom": "^18", 24 | "eslint": "^8", 25 | "eslint-config-next": "14.0.4", 26 | "typescript": "^5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696App/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "paths": { 17 | "@/*": ["./src/*"] 18 | }, 19 | "plugins": [ 20 | { 21 | "name": "next" 22 | } 23 | ] 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "issue696" 4 | ], 5 | "buildConfig": { 6 | "buildCommand": "npx --force ../../../packages/next-on-pages", 7 | "buildOutputDirectory": ".vercel/output/static" 8 | }, 9 | "deploymentConfig": { 10 | "compatibilityFlags": ["nodejs_compat"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from 'next/server'; 2 | 3 | export async function middleware(req: NextRequest) {} 4 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | trailingSlash: true, 4 | reactStrictMode: true, 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamic-routes-api-404", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "pages:build": "npx @cloudflare/next-on-pages -m", 11 | "pages:deploy": "npm run pages:build && wrangler pages deploy .vercel/output/static", 12 | "pages:watch": "npx @cloudflare/next-on-pages --watch", 13 | "pages:dev": "npx wrangler pages dev .vercel/output/static --compatibility-date=2023-11-21 --compatibility-flag=nodejs_compat" 14 | }, 15 | "dependencies": { 16 | "next": "14.0.4", 17 | "react": "^18", 18 | "react-dom": "^18" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^20", 22 | "@types/react": "^18", 23 | "@types/react-dom": "^18", 24 | "eslint": "^8", 25 | "eslint-config-next": "14.0.4", 26 | "typescript": "^5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from 'next/app'; 2 | 3 | export default function App({ Component, pageProps }: AppProps) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document'; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export default function handler(req: Request) { 4 | return Response.json({ text: 'Hello world!' }); 5 | } 6 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return ( 3 |
4 |

home page

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue696Pages/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "paths": { 17 | "@/*": ["./src/*"] 18 | } 19 | }, 20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue797app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | .wrangler -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue797app/env.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by Wrangler 2 | // by running `wrangler types --env-interface CloudflareEnv env.d.ts` 3 | 4 | interface CloudflareEnv {} 5 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue797app/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "issue797" 4 | ], 5 | "buildConfig": { 6 | "buildCommand": "npx --force ../../../packages/next-on-pages", 7 | "buildOutputDirectory": ".vercel/output/static" 8 | }, 9 | "deploymentConfig": { 10 | "compatibilityFlags": ["nodejs_compat"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue797app/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { setupDevPlatform } from '@cloudflare/next-on-pages/next-dev'; 2 | 3 | // Here we use the @cloudflare/next-on-pages next-dev module to allow us to use bindings during local development 4 | // (when running the application with `next dev`), for more information see: 5 | // https://github.com/cloudflare/next-on-pages/blob/5712c57ea7/internal-packages/next-dev/README.md 6 | if (process.env.NODE_ENV === 'development') { 7 | await setupDevPlatform(); 8 | } 9 | 10 | /** @type {import('next').NextConfig} */ 11 | const nextConfig = {}; 12 | 13 | export default nextConfig; 14 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue797app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cf-middleware-set-header", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "pages:build": "npx @cloudflare/next-on-pages -md", 11 | "pages:dev": "wrangler pages dev", 12 | "preview": "npm run pages:build && wrangler pages dev", 13 | "deploy": "npm run pages:build && wrangler pages deploy", 14 | "cf-typegen": "wrangler types --env-interface CloudflareEnv env.d.ts" 15 | }, 16 | "dependencies": { 17 | "next": "14.1.0", 18 | "react": "^18", 19 | "react-dom": "^18" 20 | }, 21 | "devDependencies": { 22 | "@cloudflare/next-on-pages": "*", 23 | "@cloudflare/workers-types": "^4.20240529.0", 24 | "@types/node": "^20", 25 | "@types/react": "^18", 26 | "@types/react-dom": "^18", 27 | "typescript": "^5", 28 | "vercel": "^34.2.8", 29 | "wrangler": "^3.58.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue797app/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | import { headers } from 'next/headers'; 3 | 4 | export const metadata: Metadata = { 5 | title: 'Create Next App', 6 | description: 'Generated by create next app', 7 | }; 8 | 9 | export default function RootLayout({ 10 | children, 11 | }: Readonly<{ 12 | children: React.ReactNode; 13 | }>) { 14 | const headerFromMiddleware = headers().get('X-header-from-middleware'); 15 | 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue797app/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { headers } from 'next/headers'; 2 | 3 | export const runtime = 'edge'; 4 | 5 | export default function Home() { 6 | const headerFromMiddleware = headers().get('X-header-from-middleware'); 7 | 8 | return ( 9 |
10 |

11 | header from middleware: {headerFromMiddleware} 12 |

13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue797app/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | 3 | export async function middleware(request: NextRequest) { 4 | const nextResponse = NextResponse.next(); 5 | nextResponse.headers.set( 6 | 'X-header-from-middleware', 7 | 'this is a header set by the middleware!', 8 | ); 9 | return nextResponse; 10 | } 11 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue797app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | }, 23 | "types": ["@cloudflare/workers-types/2023-07-01"] 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/issue797app/wrangler.toml: -------------------------------------------------------------------------------- 1 | #:schema node_modules/wrangler/config-schema.json 2 | name = "cf-middleware-set-header" 3 | compatibility_date = "2024-05-29" 4 | compatibility_flags = ["nodejs_compat"] 5 | pages_build_output_dir = ".vercel/output/static" 6 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "simplePagesApiRoutes", 4 | "simplePagesSsrRoutes", 5 | "simplePagesStaticRoutesAndAssets", 6 | "pagesMiddleware", 7 | "pagesRouting" 8 | ], 9 | "localSetup": "npm install", 10 | "buildConfig": { 11 | "buildCommand": "npx --force ../../../packages/next-on-pages", 12 | "buildOutputDirectory": ".vercel/output/static" 13 | }, 14 | "deploymentConfig": { 15 | "compatibilityFlags": ["nodejs_compat"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | experimental: { 5 | runtime: 'experimental-edge', 6 | }, 7 | swcMinify: false, 8 | env: { 9 | frameworkVersion: '12.3.0', 10 | }, 11 | }; 12 | 13 | export default nextConfig; 14 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pages12.0.0", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "12.3.0", 13 | "react": "18.0.0", 14 | "react-dom": "18.0.0" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "20.4.0", 18 | "@types/react": "18.0.0", 19 | "eslint": "7.32.0", 20 | "eslint-config-next": "12.0.0", 21 | "typescript": "5.1.6", 22 | "@cloudflare/next-on-pages": "*" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | import type { AppProps } from 'next/app'; 3 | 4 | function MyApp({ Component, pageProps }: AppProps) { 5 | return ; 6 | } 7 | 8 | export default MyApp; 9 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/pages/api/version.js: -------------------------------------------------------------------------------- 1 | // Next.js Edge API Routes: https://nextjs.org/docs/api-routes/edge-api-routes 2 | 3 | export const config = { 4 | runtime: 'experimental-edge', 5 | }; 6 | 7 | export default async function handler(req) { 8 | return new Response(process.env.frameworkVersion); 9 | } 10 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/next-on-pages/3d84ab0096258febb88193c4b9d269ecbaf1b19b/pages-e2e/fixtures/pages12.3.0/public/favicon.ico -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: 6 | -apple-system, 7 | BlinkMacSystemFont, 8 | Segoe UI, 9 | Roboto, 10 | Oxygen, 11 | Ubuntu, 12 | Cantarell, 13 | Fira Sans, 14 | Droid Sans, 15 | Helvetica Neue, 16 | sans-serif; 17 | } 18 | 19 | a { 20 | color: inherit; 21 | text-decoration: none; 22 | } 23 | 24 | * { 25 | box-sizing: border-box; 26 | } 27 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages12.3.0/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages13.0.0/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages13.0.0/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "simplePagesApiRoutes", 4 | "simplePagesSsrRoutes", 5 | "simplePagesStaticRoutesAndAssets", 6 | "pagesMiddleware", 7 | "pagesRouting", 8 | "pagesConfigsRewritesRedirectsHeaders", 9 | "pagesWasm", 10 | "pagesGetRequestContext" 11 | ], 12 | "localSetup": "npm install", 13 | "buildConfig": { 14 | "buildCommand": "npx --force ../../../packages/next-on-pages", 15 | "buildOutputDirectory": ".vercel/output/static" 16 | }, 17 | "deploymentConfig": { 18 | "compatibilityFlags": ["nodejs_compat"] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages13.0.0/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | env: { 6 | frameworkVersion: '13.0.0', 7 | }, 8 | }; 9 | 10 | export default nextConfig; 11 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages13.0.0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pages13.0.0", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "13.5.4", 13 | "react": "18.2.0", 14 | "react-dom": "18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@cloudflare/next-on-pages": "../../../packages/next-on-pages", 18 | "@types/node": "20.4.0", 19 | "@types/react": "18.2.14", 20 | "@types/react-dom": "18.2.6", 21 | "eslint": "8.44.0", 22 | "eslint-config-next": "13.0.0", 23 | "typescript": "5.1.6" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages13.0.0/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | import type { AppProps } from 'next/app'; 3 | 4 | export default function App({ Component, pageProps }: AppProps) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages13.0.0/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/next-on-pages/3d84ab0096258febb88193c4b9d269ecbaf1b19b/pages-e2e/fixtures/pages13.0.0/public/favicon.ico -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages13.0.0/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages13.0.0/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: 6 | -apple-system, 7 | BlinkMacSystemFont, 8 | Segoe UI, 9 | Roboto, 10 | Oxygen, 11 | Ubuntu, 12 | Cantarell, 13 | Fira Sans, 14 | Droid Sans, 15 | Helvetica Neue, 16 | sans-serif; 17 | } 18 | 19 | a { 20 | color: inherit; 21 | text-decoration: none; 22 | } 23 | 24 | * { 25 | box-sizing: border-box; 26 | } 27 | 28 | @media (prefers-color-scheme: dark) { 29 | html { 30 | color-scheme: dark; 31 | } 32 | body { 33 | color: white; 34 | background: black; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages13.0.0/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/components/links.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { useRouter } from 'next/router'; 3 | 4 | export default function Links() { 5 | const { pathname } = useRouter(); 6 | return ( 7 |
8 | {pathname !== '/get-static-props' && ( 9 | 10 | To getStaticProps page 11 | 12 | )} 13 | 14 | {pathname !== '/get-static-props/[slug]' && ( 15 | 19 | To dynamic getStaticProps page 20 | 21 | )} 22 | 23 | {pathname !== '/get-server-side-props' && ( 24 | 28 | To getServerSideProps page 29 | 30 | )} 31 | 32 | {pathname !== '/' && ( 33 | 34 | To index page 35 | 36 | )} 37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/components/locale-info.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | type Props = { 4 | locale?: string; 5 | locales?: string[]; 6 | }; 7 | 8 | export default function getLocaleInfo({ locale, locales }: Props) { 9 | const router = useRouter(); 10 | const { defaultLocale } = router; 11 | 12 | return ( 13 |
24 | 25 | Current locale: {locale} 26 | 27 | 28 | Default locale: {defaultLocale} 29 | 30 | 31 | Configured locales: {[...(locales ?? [])].sort()?.join(', ')} 32 | 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/components/locale-switcher.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { useRouter } from 'next/router'; 3 | 4 | export default function LocaleSwitcher() { 5 | const router = useRouter(); 6 | const { locales, locale: activeLocale } = router; 7 | 8 | const otherLocales = (locales ?? []).filter( 9 | locale => locale !== activeLocale, 10 | ); 11 | 12 | return ( 13 |
24 | Locale switcher: 25 |
    35 | {otherLocales.map(locale => { 36 | const { pathname, query, asPath } = router; 37 | return ( 38 |
  • 39 | 40 | {locale} 41 | 42 |
  • 43 | ); 44 | })} 45 |
46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "pagesConfigsI18n" 4 | ], 5 | "localSetup": "npm install", 6 | "buildConfig": { 7 | "buildCommand": "npx --force ../../../packages/next-on-pages", 8 | "buildOutputDirectory": ".vercel/output/static" 9 | }, 10 | "deploymentConfig": { 11 | "compatibilityFlags": ["nodejs_compat"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/next.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** 4 | * @type {import('next').NextConfig} 5 | **/ 6 | const nextConfig = { 7 | i18n: { 8 | locales: ['en', 'fr', 'nl', 'es'], 9 | defaultLocale: 'en', 10 | }, 11 | }; 12 | 13 | export default nextConfig; 14 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pages14.0.0_i18n", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@types/node": "18.15.3", 13 | "@types/react": "18.0.28", 14 | "@types/react-dom": "18.0.11", 15 | "eslint": "8.36.0", 16 | "eslint-config-next": "13.4.1", 17 | "next": "14.0.0", 18 | "react": "18.2.0", 19 | "react-dom": "18.2.0", 20 | "typescript": "5.0.2" 21 | }, 22 | "devDependencies": { 23 | "@cloudflare/next-on-pages": "*" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/pages/get-server-side-props.tsx: -------------------------------------------------------------------------------- 1 | import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'; 2 | 3 | import LocaleSwitcher from '../components/locale-switcher'; 4 | import Links from '../components/links'; 5 | import LocaleInfo from '../components/locale-info'; 6 | 7 | type Props = InferGetServerSidePropsType; 8 | 9 | export default function GetServeSidePropsPage(props: Props) { 10 | return ( 11 |
12 |

getServerSideProps page

13 | 14 | 15 | 16 | 17 | 18 |
19 | ); 20 | } 21 | 22 | export const getServerSideProps: GetServerSideProps<{ 23 | locale?: string; 24 | locales?: string[]; 25 | }> = async ({ locale, locales }) => { 26 | return { 27 | props: { 28 | locale, 29 | locales, 30 | }, 31 | }; 32 | }; 33 | 34 | export const runtime = 'experimental-edge'; 35 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/pages/get-static-props/[slug].tsx: -------------------------------------------------------------------------------- 1 | import type { GetStaticProps, InferGetStaticPropsType } from 'next'; 2 | import { useRouter } from 'next/router'; 3 | import LocaleSwitcher from '../../components/locale-switcher'; 4 | import Links from '../../components/links'; 5 | import LocaleInfo from '../../components/locale-info'; 6 | 7 | type Props = InferGetStaticPropsType; 8 | 9 | export default function GetStaticPropsDynamicPage(props: Props) { 10 | const router = useRouter(); 11 | const { isFallback, query } = router; 12 | 13 | if (isFallback) { 14 | return 'Loading...'; 15 | } 16 | 17 | return ( 18 |
19 |

getStaticProps (dynamic) page

20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | ); 28 | } 29 | 30 | export const getServerSideProps: GetStaticProps<{ 31 | locale?: string; 32 | locales?: string[]; 33 | }> = async ({ locale, locales }) => { 34 | return { 35 | props: { 36 | locale, 37 | locales, 38 | }, 39 | }; 40 | }; 41 | 42 | export const runtime = 'experimental-edge'; 43 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/pages/get-static-props/index.tsx: -------------------------------------------------------------------------------- 1 | import type { GetStaticProps, InferGetStaticPropsType } from 'next'; 2 | 3 | import LocaleSwitcher from '../../components/locale-switcher'; 4 | import Links from '../../components/links'; 5 | import LocaleInfo from '../../components/locale-info'; 6 | 7 | type Props = InferGetStaticPropsType; 8 | 9 | export default function GetStaticPropsPage(props: Props) { 10 | return ( 11 |
12 |

getStaticProps page

13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ); 21 | } 22 | 23 | export const getStaticProps: GetStaticProps<{ 24 | locale?: string; 25 | locales?: string[]; 26 | }> = async ({ locale, locales }) => { 27 | return { 28 | props: { 29 | locale, 30 | locales, 31 | }, 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import LocaleSwitcher from '../components/locale-switcher'; 3 | import Links from '../components/links'; 4 | import LocaleInfo from '../components/locale-info'; 5 | 6 | export default function IndexPage() { 7 | const router = useRouter(); 8 | const { locale, locales } = router; 9 | 10 | return ( 11 |
12 |

Index page

13 | 14 | 15 | 16 | 17 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pages14.0.0_i18n/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.mjs"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/README.md: -------------------------------------------------------------------------------- 1 | Reproduction of https://github.com/cloudflare/next-on-pages/issues/578 2 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "pagesIssue578" 4 | ], 5 | "localSetup": "./setup.sh", 6 | "buildConfig": { 7 | "buildCommand": "npx --force ../../../packages/next-on-pages", 8 | "buildOutputDirectory": ".vercel/output/static" 9 | }, 10 | "deploymentConfig": { 11 | "compatibilityFlags": ["nodejs_compat"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | i18n: { 5 | locales: ['en', 'dario', 'testttttt'], 6 | defaultLocale: 'en', 7 | }, 8 | }; 9 | 10 | export default nextConfig; 11 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pages-issue-578", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "pages:build": "npx @cloudflare/next-on-pages -m", 11 | "pages:deploy": "npm run pages:build && wrangler pages deploy .vercel/output/static", 12 | "pages:watch": "npx @cloudflare/next-on-pages --watch", 13 | "pages:dev": "npx wrangler pages dev .vercel/output/static --compatibility-date=2023-11-21 --compatibility-flag=nodejs_compat", 14 | "p": "npm run pages:build && npm run pages:dev" 15 | }, 16 | "dependencies": { 17 | "next": "14.0.3", 18 | "react": "^18", 19 | "react-dom": "^18" 20 | }, 21 | "devDependencies": { 22 | "@cloudflare/next-on-pages": "/Users/dario/Repos/my-repos/next-on-pages/packages/next-on-pages", 23 | "wrangler": "^3.19.0", 24 | "@types/node": "^20", 25 | "@types/react": "^18", 26 | "@types/react-dom": "^18", 27 | "typescript": "^5", 28 | "vercel": "^32.6.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/pages/[[...path]].tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import Image from 'next/image'; 3 | import styles from '@/styles/Home.module.css'; 4 | 5 | export default function Home() { 6 | return ( 7 | <> 8 | 9 | Create Next App 10 | 11 | 12 | 13 | 14 |
15 |

[[...path]].tsx Page

16 |
17 | Next.js Logo 25 |
26 |
27 |
28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '@/styles/globals.css'; 2 | import type { AppProps } from 'next/app'; 3 | 4 | export default function App({ Component, pageProps }: AppProps) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document'; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js Edge API Routes: https://nextjs.org/docs/pages/building-your-application/routing/api-routes#edge-api-routes 2 | 3 | import type { NextRequest } from 'next/server'; 4 | 5 | export const config = { 6 | runtime: 'edge', 7 | }; 8 | 9 | export default async function handler(req: NextRequest) { 10 | return new Response('Hello world'); 11 | } 12 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/next-on-pages/3d84ab0096258febb88193c4b9d269ecbaf1b19b/pages-e2e/fixtures/pagesIssue578/public/favicon.ico -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/setup.sh: -------------------------------------------------------------------------------- 1 | # No setup required -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | align-items: center; 6 | padding: 6rem; 7 | min-height: 100vh; 8 | } 9 | 10 | .center { 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | position: relative; 15 | padding: 4rem 0; 16 | } 17 | 18 | @media (prefers-color-scheme: dark) { 19 | .logo { 20 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesIssue578/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "paths": { 17 | "@/*": ["./*"] 18 | } 19 | }, 20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesLatest/main.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "features": [ 3 | "simplePagesApiRoutes", 4 | "simplePagesSsrRoutes", 5 | "simplePagesStaticRoutesAndAssets", 6 | "pagesMiddleware", 7 | "pagesRouting", 8 | "pagesRoutingSsrDynamicCatchAll", 9 | "pagesConfigsRewritesRedirectsHeaders", 10 | "pagesWasm", 11 | "pagesGetRequestContext" 12 | ], 13 | "localSetup": "./setup.sh", 14 | "buildConfig": { 15 | "buildCommand": "npx --force ../../../packages/next-on-pages", 16 | "buildOutputDirectory": ".vercel/output/static" 17 | }, 18 | "deploymentConfig": { 19 | "compatibilityFlags": ["nodejs_compat"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pages-e2e/fixtures/pagesLatest/setup.sh: -------------------------------------------------------------------------------- 1 | # Create a package.json in the workspace directory (this is needed because otherwise npm creates 2 | # the next app at the top of the pages-e2es dir instead of doing that in the current directory) 3 | # 4 | # Note: this seems to be a weird node 16 behavior it doesn't seem to happen in 18, so this can be 5 | # removed when we move to node 18 6 | echo "{}" > package.json 7 | 8 | # Create a pages application in the application sub-directory (since the next cli complains if the target directory is not empty) 9 | npx create-next-app@latest application pagesLatest --ts --no-eslint --no-tailwind --no-src-dir --no-app --import-alias '@/*' 10 | 11 | # Move everything back in the current directory (so that we follow the same structure as the other fixtures 12 | # and don't break copyWorkspaceAssets) 13 | cp -a application/. . 14 | 15 | # Delete the no longer needed application directory 16 | rm -rf application 17 | 18 | # Delete the pages/api/hello.ts as it is a default nodejs one (as we do recreate an edge version of it anyways) 19 | rm -rf ./pages/api/hello.ts -------------------------------------------------------------------------------- /pages-e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pages-e2e", 3 | "private": true, 4 | "scripts": { 5 | "//test:e2e 1": "we start with npm i because this fixes an existing bug in the pages-e2e-test-runner-cli", 6 | "//test:e2e 2": "which fails to detect test suites in the files after any run of the test-runner cli", 7 | "//": "", 8 | "//test:e2e 3": "wrangler 3.17.1 is the latest non-beta version of wrangler with which the e2es pass", 9 | "test:e2e": "npm i ; npx @cfpreview/pages-e2e-test-runner-cli --install-wrangler=3.17.1 --environment=local" 10 | }, 11 | "dependencies": { 12 | "@cfpreview/pages-e2e-test-runner-cli": "^0.0.25", 13 | "recast": "^0.23.4", 14 | "playwright": "^1.36.1", 15 | "vercel": "latest", 16 | "vitest": "0.32.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pages-e2e/test-types.d.ts: -------------------------------------------------------------------------------- 1 | declare const DEPLOYMENT_URL: string; 2 | declare const BROWSER: import('playwright').Browser; 3 | -------------------------------------------------------------------------------- /pages-e2e/tsconfig-base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "noEmit": true, 7 | "target": "ESNext" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /pages-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-base.json", 3 | "include": ["./test-types.d.ts", "./**/*.test.ts", "vitest.config.ts"], 4 | "compilerOptions": { 5 | "paths": { 6 | "@features-utils/*": ["./features/_utils/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /pages-e2e/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | retry: 3, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "outputs": ["dist/**"] 6 | }, 7 | "build:watch": { 8 | "persistent": true, 9 | "cache": false, 10 | "outputs": ["dist/**"] 11 | }, 12 | "lint": {}, 13 | "//#prettier__check": {}, 14 | "//#prettier__fix": { 15 | "cache": false 16 | }, 17 | "types-check": {}, 18 | "test:unit": { 19 | "persistent": true 20 | }, 21 | "test:e2e": {}, 22 | "publish": {} 23 | } 24 | } 25 | --------------------------------------------------------------------------------