Skip to content

[Instant] Instant validation in Dev#89077

Merged
lubieowoce merged 2 commits intocanaryfrom
lubieowoce/prefetch-validation
Feb 5, 2026
Merged

[Instant] Instant validation in Dev#89077
lubieowoce merged 2 commits intocanaryfrom
lubieowoce/prefetch-validation

Conversation

@lubieowoce
Copy link
Member

@lubieowoce lubieowoce commented Jan 27, 2026

This PR implements the initial version of dev-time validation for export const instant = ...

export const instant = { prefetch: 'static' }
export const instant = { prefetch: 'runtime', ... }
export const instant = false

(the config is currently expected to be named unstable_instant, but i'll be shortening it to instant below.)

When a segment specifies an instant config with prefetch: 'static' or prefetch: 'runtime', we'll validate that all (prefetched) navigations into that segment will render instant UI, i.e. that the navigation won't block. On the other hand, if a segment is allowed to block, it must be marked with export const instant = false.

Note that our existing static shell validation is a special case of this, but for now, we're keeping both. A future PR will reimplement static shell validation as a special case of instant validation.

Like static shell validation, these validations currently run in dev whenever we render the full page (i.e. no segments are omitted). This means it'll happen on an initial load (or refresh) and HMR.

Implementation notes

Validation approach

The goal is to simulate what the browser would display for a prefetched client navigation. We do this by re-assembling the segments extracted from the original stream for the page.

  • The outer (shared) segments should already fully resolved (i.e. in the Dynamic stage), to represent the fact that the browser loaded them before
  • The new segments are either in Static or Runtime stage, depending on how each segment would have been prefetched.

Similar to static shell validation, we then perform a Fizz prerender on the combined payload, abort it, and track the locations of onError calls. The difference here is that unlike static shell validation, we require a Suspense boundary inside the new subtree. This is detected by adding a InstantValidationBoundary around the new subtree when we construct the payload, and checking if there's a suspense below that in the component stack.

Currently, for each instant config we find, we take each of its parent layouts as a possible navigation parent (unless it's marked as blocking). So if we have a page with parent layouts like this:

/layout.tsx
/foo/layout.tsx
/foo/bar/layout.tsx
/foo/bar/page.tsx <- `export const instant = { prefetch: 'static' }`

Then we'll check for navigations where each of /layout.tsx, /foo/layout.tsx and /foo/bar/layout.tsx is a fully resolved shared parent.

If a validation fails, we do a "discriminated error message" flow analogous to static shell validation. Static segments are replaced with Runtime segments to see if the error goes away, which lets us determine if the hole is caused by runtime or dynamic data.

Building the combined payload

First, we need to separate the full stream into segments. The process is similar to collectSegmentData -- we need to deserialize the payload from the stream and then re-serialize each segment separately. The complexity here comes from the fact that the full stream is separated into stages, and we need each segment to be in staged form as well. This lets us pick and choose which stage the segment is in to simulate what we'd show for a static or runtime prefetch.

The other complex part is the "late release trick" (as implemented in createNodeStreamWithLateRelease). This was already done for static shell validation, but it's more complex here.

Debug info for dynamic holes is usually delayed until a further stage. So, in the static stage we only see an unresolved promise (or lazy reference), and then in the runtime stage we get the debugInfo telling us that it was caused by await cookies(). To ensure that this debug info is available for purposes on error reporting, we do a "late release" -- before we abort, we advance the each segment to the dynamic stage, which won't cause any new content to render, but will provide Fizz with debug info and thus give us precise error locations in onError. If you see releaseSignal, that's what it's for.

instant on layouts

We support specifying instant on layouts. The validation principle is the same, but note that in the current mode (validating a single page in dev) we can't enforce that all navigations into that layout satisfy the constraint, we can only check if that's satisfied for the current page.

Blocking segments

A segment can be marked as blocking with

export const instant = false

This is meant to signal that this segment deliberately does not have instant UI.

Currently, this is affects validation as follows:

If there's no instant config in parent segments, skip validating all navigations where this layout would be new (because we know it'd block), i.e. only validate navigations where it's a shared parent. Children of this segment can still have instant configs of their own, and those will be validated. This allows structures where a layout is blocking, but once it's loaded, navigations within it should have instant UI.

If there is an instant config in a parent segment, the validation of that parent is not skipped. The reasoning here is that if a layout asserts that navigations into it should have static UI, then a child with instant = false should not violate that. This essentially requires that the child have a loading.js or that the layout has to wrap the slot with Suspense.

Note that instant = false can also be used to opt segements out of static shell validation without requiring a Suspense above body.

Limitations and planned follow-ups

  • instant configs in parallel routes (i.e. @slot) are not currently validated.
  • expectUnableToVerify is not implemented yet, i.e. there's no way to say "this cannot be validated using SSR". The shared segments are not expected to suspend. For now, export const instant = { prefetch: ..., unstable_disableValidation: true } can be used to bypass a segment if validating it causes problems.
  • static shell validation is a special case of instant validation, but is not currently implemented as such.
  • we should be able to validate during client navigations, not just full-page loads and HMR, but we currently can't because we don't have all the segments when we do that, so we can't assemble a proper combined payload.

@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch 3 times, most recently from 0518cd7 to 7bb3210 Compare January 28, 2026 15:06
@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Jan 28, 2026

Tests Passed

@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Jan 28, 2026

Stats from current PR

🔴 1 regression

Metric Canary PR Change Trend
node_modules Size 465 MB 467 MB 🔴 +1.3 MB (+0%) ▁▁███
📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 455ms 455ms ▁▄█▁▁
Cold (Ready in log) 440ms 440ms ▅▄█▄▅
Cold (First Request) 809ms 800ms █▃▆▇▆
Warm (Listen) 456ms 457ms ▁▅█▁▁
Warm (Ready in log) 438ms 439ms ▁▄█▁▁
Warm (First Request) 340ms 340ms ▃▃█▄▄
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 455ms 455ms █▁▁▁▁
Cold (Ready in log) 435ms 434ms ▆▄▅▅▅
Cold (First Request) 1.773s 1.781s ▅▂▄▃▃
Warm (Listen) 456ms 456ms █▁▁▁▁
Warm (Ready in log) 434ms 435ms ▆▄▄▅▅
Warm (First Request) 1.782s 1.790s ▆▂▄▃▃

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.071s 4.061s ▁▅█▁▁
Cached Build 4.072s 4.105s ▁▅█▁▁
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 13.648s 13.728s ▆▁▅▁▁
Cached Build 13.763s 13.823s ▆▁▆▂▁
node_modules Size 465 MB 467 MB 🔴 +1.3 MB (+0%) ▁▁███
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles: **437 kB** → **437 kB** ⚠️ +343 B

81 files with content-based hashes (individual files not comparable between builds)

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 764 B 764 B
Total 764 B 764 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 452 B 451 B
Total 452 B 451 B ✅ -1 B

📦 Webpack

Client

Main Bundles
Canary PR Change
5528-HASH.js gzip 5.47 kB N/A -
6280-HASH.js gzip 56.9 kB N/A -
6335.HASH.js gzip 169 B N/A -
912-HASH.js gzip 4.53 kB N/A -
e8aec2e4-HASH.js gzip 62.5 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 255 B 253 B
main-HASH.js gzip 39.1 kB 39.1 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
262-HASH.js gzip N/A 4.52 kB -
2889.HASH.js gzip N/A 169 B -
5602-HASH.js gzip N/A 5.48 kB -
6948ada0-HASH.js gzip N/A 62.5 kB -
9544-HASH.js gzip N/A 57.5 kB -
Total 230 kB 231 kB ⚠️ +606 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 194 B
_error-HASH.js gzip 183 B 180 B 🟢 3 B (-2%)
css-HASH.js gzip 331 B 330 B
dynamic-HASH.js gzip 1.81 kB 1.81 kB
edge-ssr-HASH.js gzip 256 B 256 B
head-HASH.js gzip 351 B 352 B
hooks-HASH.js gzip 384 B 383 B
image-HASH.js gzip 580 B 581 B
index-HASH.js gzip 260 B 260 B
link-HASH.js gzip 2.49 kB 2.49 kB
routerDirect..HASH.js gzip 320 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.97 kB 7.97 kB ✅ -1 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 126 kB 126 kB
page.js gzip 248 kB 249 kB
Total 374 kB 375 kB ⚠️ +883 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 615 B 616 B
middleware-r..fest.js gzip 156 B 155 B
middleware.js gzip 33 kB 33.2 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 34.6 kB 34.8 kB ⚠️ +247 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 732 B 736 B
Total 732 B 736 B ⚠️ +4 B
Build Cache
Canary PR Change
0.pack gzip 3.83 MB 3.85 MB 🔴 +27.5 kB (+1%)
index.pack gzip 102 kB 104 kB 🔴 +1.5 kB (+1%)
index.pack.old gzip 103 kB 104 kB
Total 4.03 MB 4.06 MB ⚠️ +29.7 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 313 kB 315 kB 🔴 +2.25 kB (+1%)
app-page-exp..prod.js gzip 167 kB 167 kB
app-page-tur...dev.js gzip 313 kB 315 kB 🔴 +2.26 kB (+1%)
app-page-tur..prod.js gzip 167 kB 167 kB
app-page-tur...dev.js gzip 309 kB 311 kB 🔴 +2.26 kB (+1%)
app-page-tur..prod.js gzip 165 kB 165 kB
app-page.run...dev.js gzip 309 kB 312 kB 🔴 +2.25 kB (+1%)
app-page.run..prod.js gzip 165 kB 165 kB
app-route-ex...dev.js gzip 70.4 kB 70.5 kB
app-route-ex..prod.js gzip 48.9 kB 49 kB
app-route-tu...dev.js gzip 70.5 kB 70.5 kB
app-route-tu..prod.js gzip 49 kB 49 kB
app-route-tu...dev.js gzip 70.1 kB 70.1 kB
app-route-tu..prod.js gzip 48.7 kB 48.8 kB
app-route.ru...dev.js gzip 70 kB 70.1 kB
app-route.ru..prod.js gzip 48.7 kB 48.7 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 43.2 kB 43.2 kB
pages-api-tu..prod.js gzip 32.9 kB 32.9 kB
pages-api.ru...dev.js gzip 43.1 kB 43.1 kB
pages-api.ru..prod.js gzip 32.8 kB 32.8 kB
pages-turbo....dev.js gzip 52.5 kB 52.5 kB
pages-turbo...prod.js gzip 39.4 kB 39.4 kB
pages.runtim...dev.js gzip 52.4 kB 52.4 kB
pages.runtim..prod.js gzip 39.3 kB 39.3 kB
server.runti..prod.js gzip 62.7 kB 62.7 kB
Total 2.78 MB 2.79 MB ⚠️ +9.88 kB
📝 Changed Files (17 files)

Files with changes:

  • app-page-exp..ntime.dev.js
  • app-page-exp..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page.runtime.dev.js
  • app-page.runtime.prod.js
  • app-route-ex..ntime.dev.js
  • app-route-ex..time.prod.js
  • app-route-tu..ntime.dev.js
  • app-route-tu..time.prod.js
  • app-route-tu..ntime.dev.js
  • app-route-tu..time.prod.js
  • app-route.runtime.dev.js
  • app-route.ru..time.prod.js
  • server.runtime.prod.js
View diffs
app-page-exp..ntime.dev.js
failed to diff
app-page-exp..time.prod.js

Diff too large to display

app-page-tur..ntime.dev.js
failed to diff
app-page-tur..time.prod.js
failed to diff
app-page-tur..ntime.dev.js
failed to diff
app-page-tur..time.prod.js

Diff too large to display

app-page.runtime.dev.js
failed to diff
app-page.runtime.prod.js

Diff too large to display

app-route-ex..ntime.dev.js

Diff too large to display

app-route-ex..time.prod.js

Diff too large to display

app-route-tu..ntime.dev.js

Diff too large to display

app-route-tu..time.prod.js

Diff too large to display

app-route-tu..ntime.dev.js

Diff too large to display

app-route-tu..time.prod.js

Diff too large to display

app-route.runtime.dev.js

Diff too large to display

app-route.ru..time.prod.js

Diff too large to display

server.runtime.prod.js

Diff too large to display

@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch 7 times, most recently from 4a57a5b to e3abb9f Compare February 2, 2026 19:20
@lubieowoce lubieowoce changed the base branch from canary to graphite-base/89077 February 3, 2026 16:31
@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch from e3abb9f to fdc3f11 Compare February 3, 2026 16:32
@lubieowoce lubieowoce changed the base branch from graphite-base/89077 to lubieowoce/rename-prefetch February 3, 2026 16:32
Copy link
Member Author

lubieowoce commented Feb 3, 2026

@lubieowoce lubieowoce changed the title dev prefetch validation [WIP] [CC] Instant validation in dev Feb 3, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 3, 2026

Merging this PR will not alter performance

✅ 17 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing lubieowoce/prefetch-validation (fdc3f11) with lubieowoce/rename-prefetch (4698f1f)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch 2 times, most recently from acd1897 to e8a0420 Compare February 3, 2026 17:08
@lubieowoce lubieowoce force-pushed the lubieowoce/rename-prefetch branch from 4698f1f to a0248e3 Compare February 3, 2026 17:08
@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch 3 times, most recently from 081bdeb to 15b44d4 Compare February 4, 2026 12:38
@nextjs-bot nextjs-bot added the Documentation Related to Next.js' official documentation. label Feb 4, 2026
@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch from 15b44d4 to 5bb592d Compare February 4, 2026 14:43
@lubieowoce lubieowoce force-pushed the lubieowoce/rename-prefetch branch from a0248e3 to 36ec4cc Compare February 4, 2026 14:44
@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch from 5bb592d to 05c0720 Compare February 4, 2026 14:44
@lubieowoce lubieowoce changed the title [CC] Instant validation in dev [CC] Instant validation in Dev Feb 4, 2026
@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch 2 times, most recently from 781d6fa to c768ac7 Compare February 4, 2026 20:13
@lubieowoce lubieowoce force-pushed the lubieowoce/rename-prefetch branch 2 times, most recently from 7401d75 to e06eecd Compare February 4, 2026 20:42
@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch from c768ac7 to 79bbcfc Compare February 4, 2026 20:42
Base automatically changed from lubieowoce/rename-prefetch to canary February 4, 2026 21:03
@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch 3 times, most recently from bf1b81c to 7009415 Compare February 4, 2026 22:15
@lubieowoce lubieowoce marked this pull request as ready for review February 4, 2026 22:34
@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch 2 times, most recently from a4ad454 to 6d690a3 Compare February 4, 2026 23:15
Copy link
Contributor

@gnoff gnoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should get the validation to a state where it runs on client navs in dev. The reason the current heuristic sort of works for static shell validation is it runs when you do the initial page load which is where static shell comes into play. But performing a navigation in dev should ideally cause feedback if that same validation wouldn't have satisfied the instant config if it were done in prod.

I think we decided we would just pay the overhead of always doing a full render in dev

() => {
// Static stage
finalStageController.advanceStage(RenderStage.Static)
startTime = performance.now() + performance.timeOrigin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to provide this when we aren't overriding it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdym? we're returning it and use it later when reassembling the payload

if you're asking why i'm passing it into the render here, i guess that's technically not needed, just there for consistency

seedData: CacheNodeSeedData
) => void
) {
// TODO: handle head as well
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we fix this before merging?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh hmmm i'll take a look, i punted on this initially and forgot to revisit. i guess this might mean we wouldn't error for blocking things that go into the head, because we'll always use a fully resolved head

Copy link
Member Author

@lubieowoce lubieowoce Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i think it's okay to punt on this for now. the only thing that can cause blocking here is dynamic generateViewport -- right now, static shell validation will tell you to use a suspense-above-body to allow it, but that won't guard you in client navs. in the instant world i guess we want to suggest instant = false on the place that does it.

but either way, i think dynamic generateViewport is rare enough that we can follow up on this later

@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch from 6d690a3 to 0c51525 Compare February 5, 2026 16:27
@lubieowoce lubieowoce changed the title [CC] Instant validation in Dev [Instant] Instant validation in Dev Feb 5, 2026
@lubieowoce lubieowoce force-pushed the lubieowoce/prefetch-validation branch from 0c51525 to 8a8f39f Compare February 5, 2026 17:31
@lubieowoce lubieowoce merged commit 2521b8a into canary Feb 5, 2026
289 of 291 checks passed
@lubieowoce lubieowoce deleted the lubieowoce/prefetch-validation branch February 5, 2026 18:14
bgub pushed a commit to bgub/next.js that referenced this pull request Feb 5, 2026
This PR implements the initial version of dev-time validation for
`export const instant = ...`

```tsx
export const instant = { prefetch: 'static' }
```
```tsx
export const instant = { prefetch: 'runtime', ... }
```
```tsx
export const instant = false
```
(the config is currently expected to be named `unstable_instant`, but
i'll be shortening it to `instant` below.)

When a segment specifies an instant config with `prefetch: 'static'` or
`prefetch: 'runtime'`, we'll validate that all (prefetched) navigations
into that segment will render instant UI, i.e. that the navigation won't
block. On the other hand, if a segment is allowed to block, it must be
marked with `export const instant = false`.

Note that our existing static shell validation is a special case of
this, but for now, we're keeping both. A future PR will reimplement
static shell validation as a special case of instant validation.

Like static shell validation, these validations currently run in dev
whenever we render the full page (i.e. no segments are omitted). This
means it'll happen on an initial load (or refresh) and HMR.

### Implementation notes

#### Validation approach

The goal is to simulate what the browser would display for a prefetched
client navigation. We do this by re-assembling the segments extracted
from the original stream for the page.
- The outer (shared) segments should already fully resolved (i.e. in the
Dynamic stage), to represent the fact that the browser loaded them
before
- The new segments are either in Static or Runtime stage, depending on
how each segment would have been prefetched.

Similar to static shell validation, we then perform a Fizz prerender on
the combined payload, abort it, and track the locations of `onError`
calls. The difference here is that unlike static shell validation, we
require a Suspense boundary *inside* the new subtree. This is detected
by adding a `InstantValidationBoundary` around the new subtree when we
construct the payload, and checking if there's a suspense below that in
the component stack.

Currently, for each `instant` config we find, we take each of its parent
layouts as a possible navigation parent (unless it's marked as
blocking). So if we have a page with parent layouts like this:
```
/layout.tsx
/foo/layout.tsx
/foo/bar/layout.tsx
/foo/bar/page.tsx <- `export const instant = { prefetch: 'static' }`
```
Then we'll check for navigations where each of `/layout.tsx`,
`/foo/layout.tsx` and `/foo/bar/layout.tsx` is a fully resolved shared
parent.

If a validation fails, we do a "discriminated error message" flow
analogous to static shell validation. Static segments are replaced with
Runtime segments to see if the error goes away, which lets us determine
if the hole is caused by runtime or dynamic data.

#### Building the combined payload

First, we need to separate the full stream into segments. The process is
similar to `collectSegmentData` -- we need to deserialize the payload
from the stream and then re-serialize each segment separately. The
complexity here comes from the fact that the full stream is separated
into stages, and we need each segment to be in staged form as well. This
lets us pick and choose which stage the segment is in to simulate what
we'd show for a static or runtime prefetch.

The other complex part is the "late release trick" (as implemented in
`createNodeStreamWithLateRelease`). This was already done for static
shell validation, but it's more complex here.

Debug info for dynamic holes is usually delayed until a further stage.
So, in the static stage we only see an unresolved promise (or lazy
reference), and then in the runtime stage we get the debugInfo telling
us that it was caused by `await cookies()`. To ensure that this debug
info is available for purposes on error reporting, we do a "late
release" -- before we abort, we advance the each segment to the dynamic
stage, which won't cause any new content to render, but will provide
Fizz with debug info and thus give us precise error locations in
`onError`. If you see `releaseSignal`, that's what it's for.

#### `instant` on layouts

We support specifying `instant` on layouts. The validation principle is
the same, but note that in the current mode (validating a single page in
dev) we can't enforce that *all* navigations into that layout satisfy
the constraint, we can only check if that's satisfied for the current
page.

#### Blocking segments

A segment can be marked as blocking with
```tsx
export const instant = false
```
This is meant to signal that this segment deliberately does not have
instant UI.

Currently, this is affects validation as follows:

If there's no `instant` config in parent segments, skip validating all
navigations where this layout would be new (because we know it'd block),
i.e. only validate navigations where it's a shared parent. Children of
this segment can still have `instant` configs of their own, and those
will be validated. This allows structures where a layout is blocking,
but once it's loaded, navigations within it should have instant UI.

If there is an `instant` config in a parent segment, the validation of
that parent is *not* skipped. The reasoning here is that if a layout
asserts that navigations into it should have static UI, then a child
with `instant = false` should not violate that. This essentially
requires that the child have a `loading.js` or that the layout has to
wrap the slot with Suspense.

Note that `instant = false` can also be used to opt segements out of
static shell validation without requiring a Suspense above body.

### Limitations and planned follow-ups

- `instant` configs in parallel routes (i.e. `@slot`) are not currently
validated.
- `expectUnableToVerify` is not implemented yet, i.e. there's no way to
say "this cannot be validated using SSR". The shared segments are not
expected to suspend. For now, `export const instant = { prefetch: ...,
unstable_disableValidation: true }` can be used to bypass a segment if
validating it causes problems.
- static shell validation is a special case of instant validation, but
is not currently implemented as such.
- we should be able to validate during client navigations, not just
full-page loads and HMR, but we currently can't because we don't have
all the segments when we do that, so we can't assemble a proper combined
payload.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Next.js team PRs by the Next.js team. Documentation Related to Next.js' official documentation. tests type: next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants