diff --git a/packages/next/build/analysis/get-page-static-info.ts b/packages/next/build/analysis/get-page-static-info.ts index da4fb7f45e64b42..56d594a40ca47d7 100644 --- a/packages/next/build/analysis/get-page-static-info.ts +++ b/packages/next/build/analysis/get-page-static-info.ts @@ -273,8 +273,9 @@ export async function getPageStaticInfo(params: { pageFilePath: string isDev?: boolean page?: string + pageType?: 'pages' | 'app' }): Promise { - const { isDev, pageFilePath, nextConfig, page } = params + const { isDev, pageFilePath, nextConfig, page, pageType } = params const fileContent = (await tryToReadFile(pageFilePath, !isDev)) || '' if ( @@ -322,10 +323,12 @@ export async function getPageStaticInfo(params: { } } + const requiresServerRuntime = ssr || ssg || pageType === 'app' + resolvedRuntime = SERVER_RUNTIME.edge === resolvedRuntime ? SERVER_RUNTIME.edge - : ssr || ssg + : requiresServerRuntime ? resolvedRuntime || nextConfig.experimental?.runtime : undefined diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index a1dafb64bf67dda..23272df2ec9f8ec 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -374,6 +374,7 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { pageFilePath, isDev, page, + pageType: isInsideAppDir ? 'app' : 'pages', }) const isServerComponent = diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 0e20c2a9e7db697..0e6cde7e236585f 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -1339,6 +1339,7 @@ export default async function build( pagePath ), nextConfig: config, + pageType, }) : undefined diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index 607d871ac4c5b07..a8621959ee0b568 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -600,6 +600,7 @@ export default class HotReloader { pageFilePath: entryData.absolutePagePath, nextConfig: this.config, isDev: true, + pageType: isAppPath ? 'app' : 'pages', }) : {} const isServerComponent = diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index c59379af9310d4a..60ccdfbdb417273 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -381,6 +381,7 @@ export default class DevServer extends Server { nextConfig: this.nextConfig, page: rootFile, isDev: true, + pageType: isAppPath ? 'app' : 'pages', }) if (isMiddlewareFile(rootFile)) { diff --git a/packages/next/server/dev/on-demand-entry-handler.ts b/packages/next/server/dev/on-demand-entry-handler.ts index a564c71b2012212..051e4d19abe336a 100644 --- a/packages/next/server/dev/on-demand-entry-handler.ts +++ b/packages/next/server/dev/on-demand-entry-handler.ts @@ -630,6 +630,7 @@ export function onDemandEntryHandler({ pageFilePath: pagePathData.absolutePagePath, nextConfig, isDev: true, + pageType: isInsideAppDir ? 'app' : 'pages', }) const added = new Map>() diff --git a/test/e2e/app-dir/app-edge-global.test.ts b/test/e2e/app-dir/app-edge-global.test.ts new file mode 100644 index 000000000000000..8b0b6c00c2d2251 --- /dev/null +++ b/test/e2e/app-dir/app-edge-global.test.ts @@ -0,0 +1,32 @@ +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { renderViaHTTP } from 'next-test-utils' +import path from 'path' + +describe('app-dir global edge configuration', () => { + if ((global as any).isNextDeploy) { + it('should skip next deploy for now', () => {}) + return + } + + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: new FileRef(path.join(__dirname, 'app-edge-global')), + dependencies: { + react: 'latest', + 'react-dom': 'latest', + typescript: 'latest', + '@types/react': 'latest', + '@types/node': 'latest', + }, + }) + }) + afterAll(() => next.destroy()) + + it('should handle edge only routes', async () => { + const appHtml = await renderViaHTTP(next.url, '/app-edge') + expect(appHtml).toContain('

Edge!

') + }) +}) diff --git a/test/e2e/app-dir/app-edge-global/.gitignore b/test/e2e/app-dir/app-edge-global/.gitignore new file mode 100644 index 000000000000000..722d5e71d93ca0a --- /dev/null +++ b/test/e2e/app-dir/app-edge-global/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/test/e2e/app-dir/app-edge-global/app/app-edge/layout.tsx b/test/e2e/app-dir/app-edge-global/app/app-edge/layout.tsx new file mode 100644 index 000000000000000..a2f8c4b579fc523 --- /dev/null +++ b/test/e2e/app-dir/app-edge-global/app/app-edge/layout.tsx @@ -0,0 +1,11 @@ +'use client' + +// TODO-APP: support typing for useSelectedLayoutSegment +// @ts-ignore +import { useSelectedLayoutSegments } from 'next/navigation' + +export default function Layout({ children }: { children: React.ReactNode }) { + // useSelectedLayoutSegment should not be thrown + useSelectedLayoutSegments() + return children +} diff --git a/test/e2e/app-dir/app-edge-global/app/app-edge/page.tsx b/test/e2e/app-dir/app-edge-global/app/app-edge/page.tsx new file mode 100644 index 000000000000000..d4b52ad1556f515 --- /dev/null +++ b/test/e2e/app-dir/app-edge-global/app/app-edge/page.tsx @@ -0,0 +1,6 @@ +export default function Page() { + if ('EdgeRuntime' in globalThis) { + return

Edge!

+ } + return

Node!

+} diff --git a/test/e2e/app-dir/app-edge-global/app/layout.tsx b/test/e2e/app-dir/app-edge-global/app/layout.tsx new file mode 100644 index 000000000000000..079c59e3e25811b --- /dev/null +++ b/test/e2e/app-dir/app-edge-global/app/layout.tsx @@ -0,0 +1,8 @@ +export default function Root({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + ) +} diff --git a/test/e2e/app-dir/app-edge-global/next.config.js b/test/e2e/app-dir/app-edge-global/next.config.js new file mode 100644 index 000000000000000..7f76ef068e624a1 --- /dev/null +++ b/test/e2e/app-dir/app-edge-global/next.config.js @@ -0,0 +1,6 @@ +module.exports = { + experimental: { + appDir: true, + runtime: 'experimental-edge', + }, +} diff --git a/test/e2e/app-dir/app-edge-global/tsconfig.json b/test/e2e/app-dir/app-edge-global/tsconfig.json new file mode 100644 index 000000000000000..5939f6892d20a01 --- /dev/null +++ b/test/e2e/app-dir/app-edge-global/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES6", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "baseUrl": ".", + "paths": { + "@/ui/*": ["ui/*"] + }, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/test/e2e/app-dir/app-edge.test.ts b/test/e2e/app-dir/app-edge.test.ts index f6c317e6894e595..340d99f4491f58a 100644 --- a/test/e2e/app-dir/app-edge.test.ts +++ b/test/e2e/app-dir/app-edge.test.ts @@ -27,7 +27,7 @@ describe('app-dir edge SSR', () => { it('should handle edge only routes', async () => { const appHtml = await renderViaHTTP(next.url, '/app-edge') - expect(appHtml).toContain('

app-edge-ssr

') + expect(appHtml).toContain('

Edge!

') const pageHtml = await renderViaHTTP(next.url, '/pages-edge') expect(pageHtml).toContain('

pages-edge-ssr

') @@ -39,7 +39,7 @@ describe('app-dir edge SSR', () => { const content = await next.readFile(pageFile) // Update rendered content - const updatedContent = content.replace('app-edge-ssr', 'edge-hmr') + const updatedContent = content.replace('Edge!', 'edge-hmr') await next.patchFile(pageFile, updatedContent) await check(async () => { const html = await renderViaHTTP(next.url, '/app-edge') @@ -51,7 +51,7 @@ describe('app-dir edge SSR', () => { await check(async () => { const html = await renderViaHTTP(next.url, '/app-edge') return html - }, /app-edge-ssr/) + }, /Edge!/) }) } }) diff --git a/test/e2e/app-dir/app-edge/app/app-edge/page.tsx b/test/e2e/app-dir/app-edge/app/app-edge/page.tsx index ee909102491f2d1..619a3b6c8d2060c 100644 --- a/test/e2e/app-dir/app-edge/app/app-edge/page.tsx +++ b/test/e2e/app-dir/app-edge/app/app-edge/page.tsx @@ -1,4 +1,8 @@ export default function Page() { - return

app-edge-ssr

+ if ('EdgeRuntime' in globalThis) { + return

Edge!

+ } + return

Node!

} + export const runtime = 'experimental-edge' diff --git a/test/unit/parse-page-static-info.test.ts b/test/unit/parse-page-static-info.test.ts index 772081e53fb0a81..c358559ece95ebf 100644 --- a/test/unit/parse-page-static-info.test.ts +++ b/test/unit/parse-page-static-info.test.ts @@ -14,6 +14,7 @@ describe('parse page static info', () => { const { runtime, ssr, ssg } = await getPageStaticInfo({ pageFilePath: join(fixtureDir, 'page-runtime/nodejs-ssr.js'), nextConfig: createNextConfig(), + pageType: 'pages', }) expect(runtime).toBe('nodejs') expect(ssr).toBe(true) @@ -24,6 +25,7 @@ describe('parse page static info', () => { const { runtime, ssr, ssg } = await getPageStaticInfo({ pageFilePath: join(fixtureDir, 'page-runtime/nodejs.js'), nextConfig: createNextConfig(), + pageType: 'pages', }) expect(runtime).toBe(undefined) expect(ssr).toBe(false) @@ -34,6 +36,7 @@ describe('parse page static info', () => { const { runtime } = await getPageStaticInfo({ pageFilePath: join(fixtureDir, 'page-runtime/edge.js'), nextConfig: createNextConfig(), + pageType: 'pages', }) expect(runtime).toBe('experimental-edge') }) @@ -42,6 +45,7 @@ describe('parse page static info', () => { const { runtime } = await getPageStaticInfo({ pageFilePath: join(fixtureDir, 'page-runtime/static.js'), nextConfig: createNextConfig(), + pageType: 'pages', }) expect(runtime).toBe(undefined) }) @@ -50,6 +54,7 @@ describe('parse page static info', () => { const { ssr, ssg } = await getPageStaticInfo({ pageFilePath: join(fixtureDir, 'page-runtime/ssr-variable-gssp.js'), nextConfig: createNextConfig(), + pageType: 'pages', }) expect(ssr).toBe(true) expect(ssg).toBe(false) @@ -61,6 +66,7 @@ describe('fallback to the global runtime configuration', () => { const { runtime, ssr, ssg } = await getPageStaticInfo({ pageFilePath: join(fixtureDir, 'page-runtime/fallback-with-gsp.js'), nextConfig: createNextConfig('experimental-edge'), + pageType: 'pages', }) expect(runtime).toBe('experimental-edge') expect(ssr).toBe(false) @@ -71,9 +77,21 @@ describe('fallback to the global runtime configuration', () => { const { runtime, ssr, ssg } = await getPageStaticInfo({ pageFilePath: join(fixtureDir, 'page-runtime/fallback-re-export-gsp.js'), nextConfig: createNextConfig('experimental-edge'), + pageType: 'pages', }) expect(runtime).toBe('experimental-edge') expect(ssr).toBe(false) expect(ssg).toBe(true) }) + + it('should always fallback to the global runtime for app', async () => { + const { runtime, ssr, ssg } = await getPageStaticInfo({ + pageFilePath: join(fixtureDir, 'page-runtime/static.js'), + nextConfig: createNextConfig('experimental-edge'), + pageType: 'app', + }) + expect(runtime).toBe('experimental-edge') + expect(ssr).toBe(false) + expect(ssg).toBe(false) + }) })