diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts
index c772a0bcbc3bdb..bfd2d010f8de39 100644
--- a/packages/next/src/build/utils.ts
+++ b/packages/next/src/build/utils.ts
@@ -1422,7 +1422,7 @@ export async function buildAppStaticPaths({
isRevalidate: false,
experimental: {
after: false,
- dynamicIO: false,
+ dynamicIO,
},
},
},
diff --git a/packages/next/src/server/web/edge-route-module-wrapper.ts b/packages/next/src/server/web/edge-route-module-wrapper.ts
index 7a39195f6cdf89..5461de93d924a6 100644
--- a/packages/next/src/server/web/edge-route-module-wrapper.ts
+++ b/packages/next/src/server/web/edge-route-module-wrapper.ts
@@ -115,7 +115,7 @@ export class EdgeRouteModuleWrapper {
: undefined,
experimental: {
after: isAfterEnabled,
- dynamicIO: false,
+ dynamicIO: !!process.env.__NEXT_DYNAMIC_IO,
},
},
}
diff --git a/test/e2e/app-dir/dynamic-io/app/params/generate-static-params/[slug]/layout.tsx b/test/e2e/app-dir/dynamic-io/app/params/generate-static-params/[slug]/layout.tsx
new file mode 100644
index 00000000000000..8a1e8873cdfbcd
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/params/generate-static-params/[slug]/layout.tsx
@@ -0,0 +1,35 @@
+// TODO once we make fetchCache inert with dynamicIO this test is expected
+// to start failing. Right now the force cache causes the fetches to be identical
+// and we get only one prebuilt route. once we remove the caching behavior of fetchCache
+// when dynamicIO is on we will get more than one route.
+// The ideal test wouldn't even use fetchCache but at the moment the default caching for fetch
+// is to not cache and so we can't rely on the default to produce a differentiating result.
+export const fetchCache = 'default-cache'
+
+export async function generateStaticParams() {
+ const set = new Set()
+ set.add(await fetchRandom('a'))
+ set.add(await fetchRandom('a'))
+
+ return Array.from(set).map((value) => {
+ return {
+ slug: ('' + value).slice(2),
+ }
+ })
+}
+
+export default async function Layout({ children, params }) {
+ return (
+ <>
+
{await params.slug}
+
+ >
+ )
+}
+
+const fetchRandom = async (entropy: string) => {
+ const response = await fetch(
+ 'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy
+ )
+ return response.text()
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/[dyn]/async/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/[dyn]/async/route.ts
new file mode 100644
index 00000000000000..3d6f8b0c15d369
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/[dyn]/async/route.ts
@@ -0,0 +1,27 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function generateStaticParams() {
+ return [
+ {
+ dyn: '1',
+ },
+ ]
+}
+
+export async function GET(
+ request: NextRequest,
+ props: { params: Promise<{ dyn: string }> }
+) {
+ const { dyn } = await props.params
+ return new Response(
+ JSON.stringify({
+ value: getSentinelValue(),
+ type: 'dynamic params',
+ param: dyn,
+ })
+ )
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/[dyn]/sync/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/[dyn]/sync/route.ts
new file mode 100644
index 00000000000000..9b7af314b610be
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/[dyn]/sync/route.ts
@@ -0,0 +1,29 @@
+import type { NextRequest, UnsafeUnwrappedParams } from 'next/server'
+
+import { getSentinelValue } from '../../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function generateStaticParams() {
+ return [
+ {
+ dyn: '1',
+ },
+ ]
+}
+
+export async function GET(
+ request: NextRequest,
+ props: { params: Promise<{ dyn: string }> }
+) {
+ const dyn = (
+ props.params as unknown as UnsafeUnwrappedParams
+ ).dyn
+ return new Response(
+ JSON.stringify({
+ value: getSentinelValue(),
+ type: 'dynamic params',
+ param: dyn,
+ })
+ )
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-cookies/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-cookies/route.ts
new file mode 100644
index 00000000000000..e794e66562f180
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-cookies/route.ts
@@ -0,0 +1,18 @@
+import type { NextRequest } from 'next/server'
+
+import { cookies } from 'next/headers'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ const sentinel = (await cookies()).get('x-sentinel')
+ return new Response(
+ JSON.stringify({
+ value: getSentinelValue(),
+ type: 'cookies',
+ 'x-sentinel': sentinel,
+ })
+ )
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-headers/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-headers/route.ts
new file mode 100644
index 00000000000000..a5b914dd5a5569
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-headers/route.ts
@@ -0,0 +1,18 @@
+import type { NextRequest } from 'next/server'
+
+import { headers } from 'next/headers'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ const sentinel = (await headers()).get('x-sentinel')
+ return new Response(
+ JSON.stringify({
+ value: getSentinelValue(),
+ type: 'headers',
+ 'x-sentinel': sentinel,
+ })
+ )
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-stream/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-stream/route.ts
new file mode 100644
index 00000000000000..54dba383f972d7
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-stream/route.ts
@@ -0,0 +1,29 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ const result = JSON.stringify({
+ value: getSentinelValue(),
+ message: 'dynamic stream',
+ })
+ const part1 = result.slice(0, result.length / 2)
+ const part2 = result.slice(result.length / 2)
+
+ const encoder = new TextEncoder()
+ const chunks = [encoder.encode(part1), encoder.encode(part2)]
+
+ let sent = 0
+ const stream = new ReadableStream({
+ async pull(controller) {
+ controller.enqueue(chunks[sent++])
+ await new Promise((r) => setTimeout(r, 1))
+ if (sent === chunks.length) {
+ controller.close()
+ }
+ },
+ })
+ return new Response(stream)
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-url/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-url/route.ts
new file mode 100644
index 00000000000000..174123832c08df
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/dynamic-url/route.ts
@@ -0,0 +1,15 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ const search = request.nextUrl.search
+ return new Response(
+ JSON.stringify({
+ value: getSentinelValue(),
+ search,
+ })
+ )
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/fetch-cached/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/fetch-cached/route.ts
new file mode 100644
index 00000000000000..128abbc51509b2
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/fetch-cached/route.ts
@@ -0,0 +1,25 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ const fetcheda = await fetchRandomCached('a')
+ const fetchedb = await fetchRandomCached('b')
+ return new Response(
+ JSON.stringify({
+ value: getSentinelValue(),
+ random1: fetcheda,
+ random2: fetchedb,
+ })
+ )
+}
+
+const fetchRandomCached = async (entropy: string) => {
+ const response = await fetch(
+ 'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy,
+ { cache: 'force-cache' }
+ )
+ return response.text()
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/fetch-mixed/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/fetch-mixed/route.ts
new file mode 100644
index 00000000000000..4bdb6495c3d73e
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/fetch-mixed/route.ts
@@ -0,0 +1,32 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ const fetcheda = await fetchRandomCached('a')
+ const fetchedb = await fetchRandomUncached('b')
+ return new Response(
+ JSON.stringify({
+ value: getSentinelValue(),
+ random1: fetcheda,
+ random2: fetchedb,
+ })
+ )
+}
+
+const fetchRandomCached = async (entropy: string) => {
+ const response = await fetch(
+ 'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy,
+ { cache: 'force-cache' }
+ )
+ return response.text()
+}
+
+const fetchRandomUncached = async (entropy: string) => {
+ const response = await fetch(
+ 'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy
+ )
+ return response.text()
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/io-cached/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/io-cached/route.ts
new file mode 100644
index 00000000000000..35a8e265b15c1c
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/io-cached/route.ts
@@ -0,0 +1,27 @@
+import type { NextRequest } from 'next/server'
+
+import { unstable_cache as cache } from 'next/cache'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ const messagea = await getCachedMessage('hello cached fast', 0)
+ const messageb = await getCachedMessage('hello cached slow', 20)
+ return new Response(
+ JSON.stringify({
+ value: getSentinelValue(),
+ message1: messagea,
+ message2: messageb,
+ })
+ )
+}
+
+async function getMessage(echo, delay) {
+ const tag = ((Math.random() * 10000) | 0).toString(16)
+ await new Promise((r) => setTimeout(r, delay))
+ return `${tag}:${echo}`
+}
+
+const getCachedMessage = cache(getMessage)
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/io-mixed/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/io-mixed/route.ts
new file mode 100644
index 00000000000000..d143cc3363f8ef
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/io-mixed/route.ts
@@ -0,0 +1,27 @@
+import type { NextRequest } from 'next/server'
+
+import { unstable_cache as cache } from 'next/cache'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ const messagea = await getCachedMessage('hello cached fast', 0)
+ const messageb = await getMessage('hello uncached slow', 20)
+ return new Response(
+ JSON.stringify({
+ value: getSentinelValue(),
+ message1: messagea,
+ message2: messageb,
+ })
+ )
+}
+
+async function getMessage(echo, delay) {
+ const tag = ((Math.random() * 10000) | 0).toString(16)
+ await new Promise((r) => setTimeout(r, delay))
+ return `${tag}:${echo}`
+}
+
+const getCachedMessage = cache(getMessage)
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/microtask/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/microtask/route.ts
new file mode 100644
index 00000000000000..0917cb1a1e8c05
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/microtask/route.ts
@@ -0,0 +1,14 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ await Promise.resolve()
+ const response = JSON.stringify({
+ value: getSentinelValue(),
+ message: 'microtask',
+ })
+ return new Response(response)
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-stream-async/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-stream-async/route.ts
new file mode 100644
index 00000000000000..4519ca0207cb9e
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-stream-async/route.ts
@@ -0,0 +1,28 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ const response = JSON.stringify({
+ value: getSentinelValue(),
+ message: 'stream response',
+ })
+ const part1 = response.slice(0, Math.floor(response.length / 2))
+ const part2 = response.slice(Math.floor(response.length / 2))
+
+ const encoder = new TextEncoder()
+ const chunks = [encoder.encode(part1), encoder.encode(part2)]
+
+ let sent = 0
+ const stream = new ReadableStream({
+ pull(controller) {
+ controller.enqueue(chunks[sent++])
+ if (sent === chunks.length) {
+ controller.close()
+ }
+ },
+ })
+ return new Response(stream)
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-stream-sync/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-stream-sync/route.ts
new file mode 100644
index 00000000000000..4de193e1c3ce7c
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-stream-sync/route.ts
@@ -0,0 +1,28 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export function GET(request: NextRequest) {
+ const response = JSON.stringify({
+ value: getSentinelValue(),
+ message: 'stream response',
+ })
+ const part1 = response.slice(0, Math.floor(response.length / 2))
+ const part2 = response.slice(Math.floor(response.length / 2))
+
+ const encoder = new TextEncoder()
+ const chunks = [encoder.encode(part1), encoder.encode(part2)]
+
+ let sent = 0
+ const stream = new ReadableStream({
+ pull(controller) {
+ controller.enqueue(chunks[sent++])
+ if (sent === chunks.length) {
+ controller.close()
+ }
+ },
+ })
+ return new Response(stream)
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-string-async/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-string-async/route.ts
new file mode 100644
index 00000000000000..272c5818842441
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-string-async/route.ts
@@ -0,0 +1,13 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ const response = JSON.stringify({
+ value: getSentinelValue(),
+ message: 'string response',
+ })
+ return new Response(response)
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-string-sync/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-string-sync/route.ts
new file mode 100644
index 00000000000000..985e15b1ee06dd
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/static-string-sync/route.ts
@@ -0,0 +1,13 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export function GET(request: NextRequest) {
+ const response = JSON.stringify({
+ value: getSentinelValue(),
+ message: 'string response',
+ })
+ return new Response(response)
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/-edge/task/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/-edge/task/route.ts
new file mode 100644
index 00000000000000..8959b2115dc9f3
--- /dev/null
+++ b/test/e2e/app-dir/dynamic-io/app/routes/-edge/task/route.ts
@@ -0,0 +1,14 @@
+import type { NextRequest } from 'next/server'
+
+import { getSentinelValue } from '../../../getSentinelValue'
+
+export const runtime = 'edge'
+
+export async function GET(request: NextRequest) {
+ await new Promise((r) => setTimeout(r, 10))
+ const response = JSON.stringify({
+ value: getSentinelValue(),
+ message: 'task',
+ })
+ return new Response(response)
+}
diff --git a/test/e2e/app-dir/dynamic-io/app/routes/fetch-mixed/route.ts b/test/e2e/app-dir/dynamic-io/app/routes/fetch-mixed/route.ts
index a1fb31abe77253..df581af02085b6 100644
--- a/test/e2e/app-dir/dynamic-io/app/routes/fetch-mixed/route.ts
+++ b/test/e2e/app-dir/dynamic-io/app/routes/fetch-mixed/route.ts
@@ -2,6 +2,8 @@ import type { NextRequest } from 'next/server'
import { getSentinelValue } from '../../getSentinelValue'
+export const runtime = 'edge'
+
export async function GET(request: NextRequest) {
const fetcheda = await fetchRandomCached('a')
const fetchedb = await fetchRandomUncached('b')
diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.params.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.params.test.ts
index 517a994479231d..0ae20363cb84fa 100644
--- a/test/e2e/app-dir/dynamic-io/dynamic-io.params.test.ts
+++ b/test/e2e/app-dir/dynamic-io/dynamic-io.params.test.ts
@@ -2481,4 +2481,38 @@ describe('dynamic-io', () => {
}
})
})
+
+ if (!isNextDev) {
+ describe('generateStaticParams', () => {
+ it('should have dynamicIO semantics inside generateStaticParams', async () => {
+ // This test is named what we want but our current implementation is not actually correct yet.
+ // We are asserting current behavior and will update the test when we land the correct behavior
+
+ const lines: Array = next.cliOutput.split('\n')
+ let i = 0
+ while (true) {
+ const line = lines[i++]
+ if (typeof line !== 'string') {
+ throw new Error(
+ 'Could not find expected route output for /params/generate-static-params/[slug]/outputs/...'
+ )
+ }
+
+ if (line.includes('/params/generate-static-params/[slug]')) {
+ let nextLine = lines[i++]
+ expect(nextLine).toMatch(
+ /\/params\/generate-static-params\/\d+\/outputs/
+ )
+ nextLine = lines[i++]
+ // Because we force-cache we only end up with one prebuilt page.
+ // When dyanmicIO semantics are fully respected we will end up with two.
+ expect(nextLine).not.toMatch(
+ /\/params\/generate-static-params\/\d+\/outputs/
+ )
+ break
+ }
+ }
+ })
+ })
+ }
})
diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.routes.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.routes.test.ts
index 089268c77d5838..da47ddd9b6cb58 100644
--- a/test/e2e/app-dir/dynamic-io/dynamic-io.routes.test.ts
+++ b/test/e2e/app-dir/dynamic-io/dynamic-io.routes.test.ts
@@ -48,14 +48,38 @@ describe('dynamic-io', () => {
expect(json.value).toEqual('at runtime')
expect(json.search).toEqual('?foo=bar')
+
+ str = await next.render('/routes/-edge/dynamic-cookies', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.type).toEqual('cookies')
+
+ str = await next.render('/routes/-edge/dynamic-headers', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.type).toEqual('headers')
+
+ str = await next.render('/routes/-edge/dynamic-stream', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.message).toEqual('dynamic stream')
+
+ str = await next.render('/routes/-edge/dynamic-url?foo=bar', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.search).toEqual('?foo=bar')
})
it('should prerender GET route handlers that have entirely cached io (fetches)', async () => {
let str = await next.render('/routes/fetch-cached', {})
let json = JSON.parse(str)
- const random1 = json.random1
- const random2 = json.random2
+ let random1 = json.random1
+ let random2 = json.random2
if (isNextDev) {
expect(json.value).toEqual('at runtime')
@@ -79,45 +103,67 @@ describe('dynamic-io', () => {
expect(random1).toEqual(json.random1)
expect(random2).toEqual(json.random2)
}
+
+ str = await next.render('/routes/-edge/fetch-cached', {})
+ json = JSON.parse(str)
+
+ random1 = json.random1
+ random2 = json.random2
+
+ expect(json.value).toEqual('at runtime')
+ expect(typeof random1).toBe('string')
+ expect(typeof random2).toBe('string')
+
+ str = await next.render('/routes/-edge/fetch-cached', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(random1).toEqual(json.random1)
+ expect(random2).toEqual(json.random2)
})
it('should not prerender GET route handlers that have some uncached io (fetches)', async () => {
let str = await next.render('/routes/fetch-mixed', {})
let json = JSON.parse(str)
- const random1 = json.random1
- const random2 = json.random2
+ let random1 = json.random1
+ let random2 = json.random2
- if (isNextDev) {
- expect(json.value).toEqual('at runtime')
- expect(typeof random1).toBe('string')
- expect(typeof random2).toBe('string')
- } else {
- expect(json.value).toEqual('at runtime')
- expect(typeof random1).toBe('string')
- expect(typeof random2).toBe('string')
- }
+ expect(json.value).toEqual('at runtime')
+ expect(typeof random1).toBe('string')
+ expect(typeof random2).toBe('string')
str = await next.render('/routes/fetch-mixed', {})
json = JSON.parse(str)
- if (isNextDev) {
- expect(json.value).toEqual('at runtime')
- expect(random1).toEqual(json.random1)
- expect(random2).not.toEqual(json.random2)
- } else {
- expect(json.value).toEqual('at runtime')
- expect(random1).toEqual(json.random1)
- expect(random2).not.toEqual(json.random2)
- }
+ expect(json.value).toEqual('at runtime')
+ expect(random1).toEqual(json.random1)
+ expect(random2).not.toEqual(json.random2)
+
+ str = await next.render('/routes/-edge/fetch-mixed', {})
+ json = JSON.parse(str)
+
+ random1 = json.random1
+ random2 = json.random2
+
+ expect(json.value).toEqual('at runtime')
+ expect(typeof random1).toBe('string')
+ expect(typeof random2).toBe('string')
+
+ str = await next.render('/routes/-edge/fetch-mixed', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(random1).toEqual(json.random1)
+ expect(random2).not.toEqual(json.random2)
})
it('should prerender GET route handlers that have entirely cached io (unstable_cache)', async () => {
let str = await next.render('/routes/io-cached', {})
let json = JSON.parse(str)
- const message1 = json.message1
- const message2 = json.message2
+ let message1 = json.message1
+ let message2 = json.message2
if (isNextDev) {
expect(json.value).toEqual('at runtime')
@@ -141,37 +187,59 @@ describe('dynamic-io', () => {
expect(message1).toEqual(json.message1)
expect(message2).toEqual(json.message2)
}
+
+ str = await next.render('/routes/-edge/io-cached', {})
+ json = JSON.parse(str)
+
+ message1 = json.message1
+ message2 = json.message2
+
+ expect(json.value).toEqual('at runtime')
+ expect(typeof message1).toBe('string')
+ expect(typeof message2).toBe('string')
+
+ str = await next.render('/routes/-edge/io-cached', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(message1).toEqual(json.message1)
+ expect(message2).toEqual(json.message2)
})
it('should not prerender GET route handlers that have some uncached io (unstable_cache)', async () => {
let str = await next.render('/routes/io-mixed', {})
let json = JSON.parse(str)
- const message1 = json.message1
- const message2 = json.message2
+ let message1 = json.message1
+ let message2 = json.message2
- if (isNextDev) {
- expect(json.value).toEqual('at runtime')
- expect(typeof message1).toBe('string')
- expect(typeof message2).toBe('string')
- } else {
- expect(json.value).toEqual('at runtime')
- expect(typeof message1).toBe('string')
- expect(typeof message2).toBe('string')
- }
+ expect(json.value).toEqual('at runtime')
+ expect(typeof message1).toBe('string')
+ expect(typeof message2).toBe('string')
str = await next.render('/routes/io-mixed', {})
json = JSON.parse(str)
- if (isNextDev) {
- expect(json.value).toEqual('at runtime')
- expect(message1).toEqual(json.message1)
- expect(message2).not.toEqual(json.message2)
- } else {
- expect(json.value).toEqual('at runtime')
- expect(message1).toEqual(json.message1)
- expect(message2).not.toEqual(json.message2)
- }
+ expect(json.value).toEqual('at runtime')
+ expect(message1).toEqual(json.message1)
+ expect(message2).not.toEqual(json.message2)
+
+ str = await next.render('/routes/-edge/io-mixed', {})
+ json = JSON.parse(str)
+
+ message1 = json.message1
+ message2 = json.message2
+
+ expect(json.value).toEqual('at runtime')
+ expect(typeof message1).toBe('string')
+ expect(typeof message2).toBe('string')
+
+ str = await next.render('/routes/-edge/io-mixed', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(message1).toEqual(json.message1)
+ expect(message2).not.toEqual(json.message2)
})
it('should prerender GET route handlers that complete synchronously or in a microtask', async () => {
@@ -229,6 +297,38 @@ describe('dynamic-io', () => {
expect(json.value).toEqual('at buildtime')
expect(json.message).toBe('string response')
}
+
+ // Edge versions are always dynamic
+
+ str = await next.render('/routes/-edge/microtask', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.message).toBe('microtask')
+
+ str = await next.render('/routes/-edge/static-stream-sync', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.message).toBe('stream response')
+
+ str = await next.render('/routes/-edge/static-stream-async', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.message).toBe('stream response')
+
+ str = await next.render('/routes/-edge/static-string-sync', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.message).toBe('string response')
+
+ str = await next.render('/routes/-edge/static-string-async', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.message).toBe('string response')
})
it('should not prerender GET route handlers that complete in a new Task', async () => {
@@ -237,6 +337,12 @@ describe('dynamic-io', () => {
expect(json.value).toEqual('at runtime')
expect(json.message).toBe('task')
+
+ str = await next.render('/routes/-edge/task', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.message).toBe('task')
})
it('should prerender GET route handlers when accessing awaited params', async () => {
@@ -270,6 +376,23 @@ describe('dynamic-io', () => {
expect(json.param).toBe('2')
expect(getLines('In route /routes/[dyn]')).toEqual([])
}
+
+ // Edge versions are always dynamic
+ str = await next.render('/routes/-edge/1/async', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.type).toBe('dynamic params')
+ expect(json.param).toBe('1')
+ expect(getLines('In route /routes/-edge/[dyn]')).toEqual([])
+
+ str = await next.render('/routes/-edge/2/async', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.type).toBe('dynamic params')
+ expect(json.param).toBe('2')
+ expect(getLines('In route /routes/-edge/[dyn]')).toEqual([])
})
it('should prerender GET route handlers when accessing params without awaiting first', async () => {
@@ -311,5 +434,38 @@ describe('dynamic-io', () => {
expect(json.param).toBe('2')
expect(getLines('In route /routes/[dyn]')).toEqual([])
}
+
+ // Edge versions are always dynamic
+ str = await next.render('/routes/-edge/1/sync', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.type).toBe('dynamic params')
+ expect(json.param).toBe('1')
+ if (isNextDev) {
+ expect(getLines('In route /routes/-edge/[dyn]')).toEqual([
+ expect.stringContaining(
+ 'a param property was accessed directly with `params.dyn`.'
+ ),
+ ])
+ } else {
+ expect(getLines('In route /routes/-edge/[dyn]')).toEqual([])
+ }
+
+ str = await next.render('/routes/-edge/2/sync', {})
+ json = JSON.parse(str)
+
+ expect(json.value).toEqual('at runtime')
+ expect(json.type).toBe('dynamic params')
+ expect(json.param).toBe('2')
+ if (isNextDev) {
+ expect(getLines('In route /routes/-edge/[dyn]')).toEqual([
+ expect.stringContaining(
+ 'a param property was accessed directly with `params.dyn`.'
+ ),
+ ])
+ } else {
+ expect(getLines('In route /routes/-edge/[dyn]')).toEqual([])
+ }
})
})