[pull] canary from vercel:canary#1041
Merged
Merged
Conversation
#93287) ### What? Extends the card-based instant error overlay (#92638) to metadata, viewport, and sync IO errors, and updates the matching build/CLI messages to a consistent structured format. ### Why? After the blocking-route redesign, metadata, viewport, and sync IO errors still used the old prose format with no visual fix guidance. ### Demo - Metadata: [runtime](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/12-cookies-in-metadata) · [dynamic](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/13-fetch-in-metadata) - Viewport: [runtime](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/14-cookies-in-viewport) · [dynamic](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/15-fetch-in-viewport) - Sync IO: [Math.random()](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/38-math-random-no-instant) · [Date.now()](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/39-date-now-no-instant) · [crypto.randomUUID()](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/40-crypto-random-no-instant) - Sync IO in Client: [Math.random()](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/44-client-math-random-no-suspense) · [Date.now()](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/43-client-date-no-suspense) · [crypto.randomUUID()](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/45-client-crypto-no-suspense) - [Fix overview (all cards)](https://error-messages-overhaul-ibsl.labs.vercel.dev/fix-overview) ### How? **Overlay** - New `dynamic-metadata`, `dynamic-viewport`, and `sync-io` error types in `errors.tsx`, all rendered through `InstantRuntimeError` with kind-specific fix cards - `isSyncIOError()` detects sync IO via the docs URL pattern, mirroring `isRuntimeVariant()` **Build & CLI messages** - Headlines aligned to "Next.js encountered..." across all error families - `blocking-route-messages.ts`: metadata/viewport errors restructured from prose to "Ways to fix this:" bullets - Sync IO messages extracted into `sync-io-messages.ts`, mirroring `blocking-route-messages.ts`. Helpers renamed to `createSyncIOError` / `createSyncIORuntimeError` / `createSyncIOClientError` to match the `create*Error` pattern
- [x] fix tests - [ ] add experimental flag and only enable in adapter? or is this cheap enough to just always do? - [x] disable for webpack Closes PACK-6541 For nextjs/adapter-vercel#24 Newer version of #89534 - Add a hashes mapping to the NFT file - Forward that information to the adapter via `assetsHashes` for functions Basically zero-cost: ``` commit ad728b1 (HEAD -> mischnic/server-paths-manifest-2, origin/mischnic/server-paths-manifest-2) pnpm next build 351.22s user, 51.55s system, 811% cpu, 49.654 total pnpm next build 354.52s user, 53.39s system, 837% cpu, 48.706 total pnpm next build 344.02s user, 63.62s system, 749% cpu, 54.368 total pnpm next build 353.74s user, 54.25s system, 833% cpu, 48.970 total commit 04294cb (origin/canary, origin/HEAD) pnpm next build 344.24s user, 53.88s system, 798% cpu, 49.845 total pnpm next build 340.89s user, 52.40s system, 784% cpu, 50.138 total pnpm next build 347.26s user, 51.91s system, 802% cpu, 49.741 total pnpm next build 347.04s user, 52.65s system, 803% cpu, 49.764 total ```
When `cacheComponents` is enabled, the App Router preserves state across
navigations by rendering inactive routes inside React `<Activity>`
boundaries.
As a default behavior, this is a huge convenience because it lets you
navigate between routes without resetting or losing ephemeral UI state
(scroll position, expand/collapse, in-progress form edits). Previously
the only way to do this was to explicitly track each state with an
external state manager, or hoist it to a parent component.
It's still the often the case that you should be explicitly tracking all
important UI state, anyway, so that it survives a hard refresh of the
app, or the browser window being accidentally closed. For example, forms
draft states should be persisted to a server-side database or local
storage engine. If you're already doing that, then it doesn't matter so
much whether client state is preserved via `<Activity>` boundaries or
not.
For the long tail of ephemeral state that is not tracked, Next.js's
philosophy is that it's a better UX default to preserve as much
emphemeral state as possible. It's easier to model the cases where you
_do_ want state to be reset on navigation as exceptions, compared to the
other way around.
However, this is a significant change compared to the pre-Cache
Components previous behavior of Next.js, and compared to other web
frameworks, and indeed the browser's own native bfcache (which
implements state restoration for history traversal navigations only, not
push/replace).
There's are also lots of existing codebases that may rely on the current
behavior, and may break subtly under these new semantics. Even if this
new default unlocks better UX patterns, we don't want to force everyone
to migrate all their code all at once.
So, this PR introduces a drop-in mechanism for opting out of state
preservation when navigating to a previously visited route.
The API is exposed as `useRouter().bfcacheId`. It's intended to be
passed to a React `key`:
<form key={useRouter().bfcacheId}>
The id is contextual: read from a layout, you get the layout's id; read
from a page, you get the page's id. It's stable across back/forward
navigations, `router.refresh()`, server actions that call `refresh()`,
and search-param- or hash-only navigations — i.e., any time the
surrounding segment is preserved. It changes when the segment is freshly
created by a push or replace into a different route.
An important detail is that the previous id is restored during a back/
foward navigation. So state preservation will still work if you navigate
via the browser's back button.
Why add this to `useRouter()` instead of giving it its own hook? The
intent is communicate that `bfcacheId` is not considered an idiomatic
pattern — the recommended fix for "I want this state to reset on
navigation" is almost always something else: an explicit reset in a
submit handler, or a key derived from the underlying data (e.g., a draft
id from the server). `useRouter` is the hook where we expose low-level
APIs that are supported but are only recommended for advanced or
exceptional cases. (For example, instead of `router.push()`, you should
almost always use a `<Link>` component instead.)
This file contains hidden or 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
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )