Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
"eslint-plugin-jsdoc": "48.0.4",
"eslint-plugin-mdx": "3.1.5",
"eslint-plugin-react": "7.37.0",
"eslint-plugin-react-hooks": "0.0.0-experimental-4842fbea-20260217",
"eslint-plugin-react-hooks": "0.0.0-experimental-2ba30655-20260219",
"event-stream": "4.0.1",
"execa": "2.0.3",
"expect": "29.7.0",
Expand Down Expand Up @@ -261,16 +261,16 @@
"pretty-ms": "7.0.0",
"random-seed": "0.3.0",
"react": "19.0.0",
"react-builtin": "npm:react@19.3.0-canary-4842fbea-20260217",
"react-builtin": "npm:react@19.3.0-canary-2ba30655-20260219",
"react-dom": "19.0.0",
"react-dom-builtin": "npm:react-dom@19.3.0-canary-4842fbea-20260217",
"react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-4842fbea-20260217",
"react-experimental-builtin": "npm:react@0.0.0-experimental-4842fbea-20260217",
"react-is-builtin": "npm:react-is@19.3.0-canary-4842fbea-20260217",
"react-server-dom-turbopack": "19.3.0-canary-4842fbea-20260217",
"react-server-dom-turbopack-experimental": "npm:react-server-dom-turbopack@0.0.0-experimental-4842fbea-20260217",
"react-server-dom-webpack": "19.3.0-canary-4842fbea-20260217",
"react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-4842fbea-20260217",
"react-dom-builtin": "npm:react-dom@19.3.0-canary-2ba30655-20260219",
"react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-2ba30655-20260219",
"react-experimental-builtin": "npm:react@0.0.0-experimental-2ba30655-20260219",
"react-is-builtin": "npm:react-is@19.3.0-canary-2ba30655-20260219",
"react-server-dom-turbopack": "19.3.0-canary-2ba30655-20260219",
"react-server-dom-turbopack-experimental": "npm:react-server-dom-turbopack@0.0.0-experimental-2ba30655-20260219",
"react-server-dom-webpack": "19.3.0-canary-2ba30655-20260219",
"react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-2ba30655-20260219",
"react-ssr-prepass": "1.0.8",
"react-virtualized": "9.22.3",
"relay-compiler": "13.0.2",
Expand All @@ -280,8 +280,8 @@
"resolve-from": "5.0.0",
"sass": "1.54.0",
"satori": "0.15.2",
"scheduler-builtin": "npm:scheduler@0.28.0-canary-4842fbea-20260217",
"scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-4842fbea-20260217",
"scheduler-builtin": "npm:scheduler@0.28.0-canary-2ba30655-20260219",
"scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-2ba30655-20260219",
"seedrandom": "3.0.5",
"semver": "7.3.7",
"serve-handler": "6.1.6",
Expand Down Expand Up @@ -326,10 +326,10 @@
"@types/react-dom": "19.2.3",
"@types/retry": "0.12.0",
"jest-snapshot": "30.0.0-alpha.6",
"react": "19.3.0-canary-4842fbea-20260217",
"react-dom": "19.3.0-canary-4842fbea-20260217",
"react-is": "19.3.0-canary-4842fbea-20260217",
"scheduler": "0.28.0-canary-4842fbea-20260217"
"react": "19.3.0-canary-2ba30655-20260219",
"react-dom": "19.3.0-canary-2ba30655-20260219",
"react-is": "19.3.0-canary-2ba30655-20260219",
"scheduler": "0.28.0-canary-2ba30655-20260219"
},
"packageExtensions": {
"eslint-plugin-react-hooks@0.0.0-experimental-6de32a5a-20250822": {
Expand Down
3 changes: 2 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -1064,5 +1064,6 @@
"1063": "`connection` must not be used within a Client Component. Next.js should be preventing `connection` from being included in Client Components statically, but did not in this case.",
"1064": "createServerParamsForRoute should not be called in client contexts.",
"1065": "createServerPathnameForMetadata should not be called in client contexts.",
"1066": "createServerSearchParamsForServerPage should not be called in a client validation."
"1066": "createServerSearchParamsForServerPage should not be called in a client validation.",
"1067": "The Next.js unhandled rejection filter is being installed more than once. This is a bug in Next.js."
}
9 changes: 9 additions & 0 deletions packages/next/src/build/templates/app-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,15 @@ export async function handler(
body.push(
new ReadableStream({
start(controller) {
if (isInstantNavigationTest) {
// Inject a global so the client can detect that this response
// is a partial static shell, independent of document.cookie
// (which may be empty on the new page in some browsers).
const encoder = new TextEncoder()
controller.enqueue(
encoder.encode('<script>self.__next_instant_test=1</script>')
)
}
controller.enqueue(ENCODED_TAGS.CLOSED.BODY_AND_HTML)
controller.close()
},
Expand Down
47 changes: 25 additions & 22 deletions packages/next/src/client/app-bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,29 +76,32 @@ export function appBootstrap(hydrate: (assetPrefix: string) => void) {
}
}

// Instant Navigation Testing API: If the page was loaded with the instant
// test cookie set (from an MPA navigation while locked), skip hydration
// and set up a minimal testing API. Hydration would fail because the
// static shell response doesn't include the full Flight data stream.
// When unlock() is called, we clear the cookie and reload the page.
// Instant Navigation Testing API: If the server returned a partial static
// shell (indicated by the __next_instant_test global injected into the
// HTML), skip hydration. The response doesn't include the full Flight data
// stream. When the test framework deletes the cookie, the CookieStore
// change event triggers a page reload.
if (process.env.__NEXT_EXPOSE_TESTING_API) {
const NEXT_INSTANT_TEST_COOKIE = 'next-instant-navigation-testing'
if (document.cookie.includes(NEXT_INSTANT_TEST_COOKIE + '=')) {
// Set up minimal testing API for the static shell
window.__EXPERIMENTAL_NEXT_TESTING__ = {
navigation: {
lock: () => {
console.error(
'Navigation lock already acquired. Concurrent locks are not allowed. ' +
'Did you forget to release the previous lock?'
)
},
unlock: () => {
// Clear the cookie and reload to get the full page with dynamic data
document.cookie = `${NEXT_INSTANT_TEST_COOKIE}=;path=/;max-age=0`
window.location.reload()
},
},
if (self.__next_instant_test) {
const NEXT_INSTANT_TEST_COOKIE = 'next-instant-navigation-testing'
if (
typeof cookieStore !== 'undefined' &&
document.cookie.includes(NEXT_INSTANT_TEST_COOKIE + '=')
) {
// Cookie is still set. Wait for the test framework to delete it,
// then reload to get the full response.
cookieStore.addEventListener('change', (event: CookieChangeEvent) => {
for (const cookie of event.deleted) {
if (cookie.name === NEXT_INSTANT_TEST_COOKIE) {
window.location.reload()
return
}
}
})
} else {
// Cookie is already gone (or not accessible). Refresh immediately
// to get the full response.
window.location.reload()
}
return
}
Expand Down
15 changes: 5 additions & 10 deletions packages/next/src/client/app-globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,11 @@ if (process.env.__NEXT_DEV_SERVER) {
require('../next-devtools/userspace/app/app-dev-overlay-setup') as typeof import('../next-devtools/userspace/app/app-dev-overlay-setup')
}

// Expose a testing API that allows e2e tests to assert on the prefetched UI
// state before dynamic data streams in. Browser-only.
// Start listening for the instant navigation test cookie. The test framework
// (e.g. Playwright) sets/clears this cookie to control the navigation lock.
// Browser-only.
if (process.env.__NEXT_EXPOSE_TESTING_API && typeof window !== 'undefined') {
const { acquireNavigationLock, releaseNavigationLock } =
const { startListeningForInstantNavigationCookie } =
require('./components/segment-cache/navigation-testing-lock') as typeof import('./components/segment-cache/navigation-testing-lock')

window.__EXPERIMENTAL_NEXT_TESTING__ = {
navigation: {
lock: acquireNavigationLock,
unlock: releaseNavigationLock,
},
}
startListeningForInstantNavigationCookie()
}
10 changes: 0 additions & 10 deletions packages/next/src/client/app-index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,6 @@ declare global {
*/
__next_r?: string
__next_f: NextFlight
/**
* Testing API that allows e2e tests to assert on the prefetched UI state
* before dynamic data streams in. Dev-only.
*/
__EXPERIMENTAL_NEXT_TESTING__?: {
navigation: {
lock: () => void
unlock: () => void
}
}
}
}

Expand Down
Loading
Loading