-
Notifications
You must be signed in to change notification settings - Fork 27.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(after): no request APIs in force-static (#73321)
Fixes a bug where `after(() => headers())` was allowed if `force-static` was on (and `cookies()` + `connection()` too). We shouldn't allow that, because it essentially just hides an error -- if the page is switched to dynamic, the same code would now start erroring. *(Note that this is still without the upcoming changes to `headers()` in `after`, so the behavior in route handlers will change. i discovered this bug while working on that update, but it was confusing to try and change both at the same time, so i pulled it out into a separate PR)*
- Loading branch information
1 parent
64f2702
commit d00ad51
Showing
14 changed files
with
271 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export const metadata = { | ||
title: 'Next.js', | ||
description: 'Generated by Next.js', | ||
} | ||
|
||
export default function RootLayout({ children }) { | ||
return ( | ||
<html lang="en"> | ||
<body>{children}</body> | ||
</html> | ||
) | ||
} |
31 changes: 31 additions & 0 deletions
31
test/e2e/app-dir/next-after-app-api-usage/app/request-apis/helpers.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { cookies, headers } from 'next/headers' | ||
import { unstable_after as after, connection } from 'next/server' | ||
|
||
export function testRequestAPIs() { | ||
after(async () => { | ||
try { | ||
await headers() | ||
console.log('headers(): ok') | ||
} catch (err) { | ||
console.error(err) | ||
} | ||
}) | ||
|
||
after(async () => { | ||
try { | ||
await cookies() | ||
console.log('cookies(): ok') | ||
} catch (err) { | ||
console.error(err) | ||
} | ||
}) | ||
|
||
after(async () => { | ||
try { | ||
await connection() | ||
console.log('connection(): ok') | ||
} catch (err) { | ||
console.error(err) | ||
} | ||
}) | ||
} |
8 changes: 8 additions & 0 deletions
8
test/e2e/app-dir/next-after-app-api-usage/app/request-apis/page-dynamic-error/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { testRequestAPIs } from '../helpers' | ||
|
||
export const dynamic = 'error' | ||
|
||
export default async function Page() { | ||
testRequestAPIs() | ||
return null | ||
} |
8 changes: 8 additions & 0 deletions
8
test/e2e/app-dir/next-after-app-api-usage/app/request-apis/page-dynamic/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { connection } from 'next/server' | ||
import { testRequestAPIs } from '../helpers' | ||
|
||
export default async function Page() { | ||
await connection() | ||
testRequestAPIs() | ||
return null | ||
} |
8 changes: 8 additions & 0 deletions
8
test/e2e/app-dir/next-after-app-api-usage/app/request-apis/page-force-static/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { testRequestAPIs } from '../helpers' | ||
|
||
export const dynamic = 'force-static' | ||
|
||
export default async function Page() { | ||
testRequestAPIs() | ||
return null | ||
} |
8 changes: 8 additions & 0 deletions
8
...2e/app-dir/next-after-app-api-usage/app/request-apis/route-handler-dynamic-error/route.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { testRequestAPIs } from '../helpers' | ||
|
||
export const dynamic = 'error' | ||
|
||
export async function GET() { | ||
testRequestAPIs() | ||
return new Response() | ||
} |
6 changes: 6 additions & 0 deletions
6
test/e2e/app-dir/next-after-app-api-usage/app/request-apis/route-handler-dynamic/route.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { testRequestAPIs } from '../helpers' | ||
|
||
export async function GET() { | ||
testRequestAPIs() | ||
return new Response() | ||
} |
8 changes: 8 additions & 0 deletions
8
...e2e/app-dir/next-after-app-api-usage/app/request-apis/route-handler-force-static/route.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { testRequestAPIs } from '../helpers' | ||
|
||
export const dynamic = 'force-static' | ||
|
||
export async function GET() { | ||
testRequestAPIs() | ||
return new Response() | ||
} |
14 changes: 14 additions & 0 deletions
14
test/e2e/app-dir/next-after-app-api-usage/app/request-apis/server-action/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { testRequestAPIs } from '../helpers' | ||
|
||
export default async function Page() { | ||
return ( | ||
<form | ||
action={async () => { | ||
'use server' | ||
testRequestAPIs() | ||
}} | ||
> | ||
<button type="submit">Submit</button> | ||
</form> | ||
) | ||
} |
143 changes: 143 additions & 0 deletions
143
test/e2e/app-dir/next-after-app-api-usage/index.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/* eslint-env jest */ | ||
import { nextTestSetup } from 'e2e-utils' | ||
import { retry } from 'next-test-utils' | ||
|
||
describe('nextjs APIs in unstable_after()', () => { | ||
const { next, skipped, isNextDev } = nextTestSetup({ | ||
files: __dirname, | ||
skipStart: true, | ||
skipDeployment: true, // reading runtime logs is not supported in deploy tests | ||
}) | ||
|
||
if (skipped) return | ||
|
||
let currentCliOutputIndex = 0 | ||
|
||
const ignorePreviousLogs = () => { | ||
currentCliOutputIndex = next.cliOutput.length | ||
} | ||
|
||
const getLogs = () => { | ||
return next.cliOutput.slice(currentCliOutputIndex) | ||
} | ||
|
||
beforeEach(() => { | ||
ignorePreviousLogs() | ||
}) | ||
|
||
let buildLogs: string | ||
beforeAll(async () => { | ||
if (!isNextDev) { | ||
await next.build() | ||
buildLogs = next.cliOutput | ||
} else { | ||
buildLogs = '(no build logs in dev)' | ||
} | ||
await next.start() | ||
}) | ||
|
||
describe('request APIs cannot be called inside unstable_after()', () => { | ||
it('in a dynamic page', async () => { | ||
const path = '/request-apis/page-dynamic' | ||
await next.render(path) | ||
await retry(() => { | ||
expect(getLogs()).toContain( | ||
`Error: Route ${path} used "headers" inside "unstable_after(...)". This is not supported.` | ||
) | ||
expect(getLogs()).toContain( | ||
`Error: Route ${path} used "cookies" inside "unstable_after(...)". This is not supported.` | ||
) | ||
expect(getLogs()).toContain( | ||
`Error: Route ${path} used "connection" inside "unstable_after(...)".` | ||
) | ||
}) | ||
}) | ||
|
||
describe('in a prerendered page', () => { | ||
it.each([ | ||
{ | ||
title: 'with `dynamic = "error"`', | ||
path: '/request-apis/page-dynamic-error', | ||
}, | ||
{ | ||
title: 'with `dynamic = "force-static"`', | ||
path: '/request-apis/page-force-static', | ||
}, | ||
])('$title', async ({ path }) => { | ||
await next.render(path) | ||
await retry(() => { | ||
const logs = isNextDev ? getLogs() : buildLogs // in `next start` the error was logged at build time | ||
expect(logs).toContain( | ||
`Error: Route ${path} used "headers" inside "unstable_after(...)". This is not supported.` | ||
) | ||
expect(logs).toContain( | ||
`Error: Route ${path} used "cookies" inside "unstable_after(...)". This is not supported.` | ||
) | ||
expect(logs).toContain( | ||
`Error: Route ${path} used "connection" inside "unstable_after(...)".` | ||
) | ||
}) | ||
}) | ||
}) | ||
|
||
it('in server actions', async () => { | ||
const path = '/request-apis/server-action' | ||
const browser = await next.browser(path) | ||
await browser.elementByCss('button[type="submit"]').click() | ||
await retry(() => { | ||
expect(getLogs()).toContain( | ||
`Error: Route ${path} used "headers" inside "unstable_after(...)". This is not supported.` | ||
) | ||
expect(getLogs()).toContain( | ||
`Error: Route ${path} used "cookies" inside "unstable_after(...)". This is not supported.` | ||
) | ||
expect(getLogs()).toContain( | ||
`Error: Route ${path} used "connection" inside "unstable_after(...)".` | ||
) | ||
}) | ||
}) | ||
|
||
it('in a dynamic route handler', async () => { | ||
const path = '/request-apis/route-handler-dynamic' | ||
await next.render(path) | ||
await retry(() => { | ||
expect(getLogs()).toContain( | ||
`Error: Route ${path} used "headers" inside "unstable_after(...)". This is not supported.` | ||
) | ||
expect(getLogs()).toContain( | ||
`Error: Route ${path} used "cookies" inside "unstable_after(...)". This is not supported.` | ||
) | ||
expect(getLogs()).toContain( | ||
`Error: Route ${path} used "connection" inside "unstable_after(...)".` | ||
) | ||
}) | ||
}) | ||
|
||
describe('in a prerendered route handler', () => { | ||
it.each([ | ||
{ | ||
title: 'with `dynamic = "error"`', | ||
path: '/request-apis/route-handler-dynamic-error', | ||
}, | ||
{ | ||
title: 'with `dynamic = "force-static"`', | ||
path: '/request-apis/route-handler-force-static', | ||
}, | ||
])('$title', async ({ path }) => { | ||
await next.render(path) | ||
await retry(() => { | ||
const logs = isNextDev ? getLogs() : buildLogs // in `next start` the error was logged at build time | ||
expect(logs).toContain( | ||
`Error: Route ${path} used "headers" inside "unstable_after(...)". This is not supported.` | ||
) | ||
expect(logs).toContain( | ||
`Error: Route ${path} used "cookies" inside "unstable_after(...)". This is not supported.` | ||
) | ||
expect(logs).toContain( | ||
`Error: Route ${path} used "connection" inside "unstable_after(...)".` | ||
) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** @type {import('next').NextConfig} */ | ||
module.exports = { | ||
experimental: { | ||
after: true, | ||
}, | ||
} |