Commit Graph

3 Commits

Author SHA1 Message Date
Andrew Clark dfa7f4f713 Attempt static prefetch before resorting to runtime (#96095)
If we're reasonably confident that a segment can be prefetched
statically without omitting data that would have been included during a
runtime prefetch (e.g. cookies), the client should attempt prefetch the
segment statically instead of going straight to a runtime request.

The decision for whether to do this is based on the
ShouldAttemptStaticPrefetch added in previous steps.

If it turns out the static response is not sufficient, then it will fall
back to a runtime request.

This makes prefetching cheaper for pages that are fully statically
renderable. We can make the optimization better in the future with more
reliable per-segment computation of the ShouldAttemptStaticPrefetch, but
the current approach should at least work for fully static pages, which
is what's most important.

This optimization applies during both the Shell phase and the
Speculative phase of the prefetching algorithm.
2026-07-28 11:51:29 -04:00
Andrew Clark 0e6ab3f548 [Prefetch Inlining] Generate size-based hints on server (#90891)
Part 1 of 2. This commit adds the server-side infrastructure for
size-based segment bundling but does not change any observable behavior.
The client-side changes that actually consume bundled responses are in
the next commit.

At build time, a measurement pass renders each segment's prefetch
response, measures its gzip size, and decides which segments should be
bundled together vs fetched separately. The decisions are persisted to a
manifest and embedded into the route tree prefetch response so the
client can act on them.

The decisions are computed once at build and remain fixed for the
lifetime of the deployment. They are not recomputed during
ISR/revalidation — if they could change, the client would need to
re-fetch the route tree after every revalidation, defeating the purpose
of caching it independently.

Refer to the next commit for a full description of the design and
motivation.

## Config

experimental.prefetchInlining accepts either a boolean or an object with
threshold overrides (maxSize, maxBundleSize). When true, the default
thresholds are used (2KB per-segment, 10KB total budget). The auto
behavior will eventually become the default. The config will remain
available for overriding thresholds.
2026-03-13 12:09:25 -04:00
Andrew Clark 9ce8d136f8 experimental.prefetchInlining: bundle segment prefetches into a single response (#90555)
## Background

Next.js 16 introduced per-segment prefetching through the Client Segment
Cache. Rather than fetching all data for a route in a single request,
the client issues individual requests for each segment in the route
tree. This design improves cache efficiency: shared layouts between
sibling routes (e.g., /dashboard/settings and /dashboard/profile sharing
a /dashboard layout) are fetched once and reused from the client cache,
avoiding redundant data transfer.

The trade-off is request volume. A route with N segments now produces N
prefetch requests instead of one. Users upgrading from older versions
notice significantly more network activity in their devtools, even
though the total bytes transferred may be similar or lower due to
deduplication.

Per-segment fetching is still a reasonable default for many sites. These
prefetch requests are served from cache, they're fast, and they run in
parallel. The main scenario where the trade-off breaks down is
deployment environments that charge per-request. But even setting aside
cost, there is a theoretical performance threshold where very small
segments are better off inlined — the per-request overhead (connection
setup, headers, scheduling) exceeds the cost of transferring duplicate
bytes. This is analogous to JS bundlers, which inline small modules
rather than creating separate chunks, because the overhead of an
additional script tag or dynamic import outweighs the bytes saved.

## What this change does

This adds `experimental.prefetchInlining`, a boolean option in
next.config.js. When enabled, the server bundles all segment data for a
route into a single `/_inlined` response rather than serving each
segment individually. The tree prefetch (`/_tree`), which provides route
structure metadata, remains a separate request — but optimistic routing
(#88965) eliminates that request entirely by predicting the route
structure client-side. With both features enabled, prefetching is
effectively one request per link.

The fundamental trade-off is straightforward: inlining reduces request
count at the cost of deduplication. Each inlined response includes its
own copy of any shared layout data, so two sibling routes will each
transfer the shared layout rather than sharing a single cached copy.
This is the same trade-off that compilers and bundlers face when
deciding whether to inline a function: inlining eliminates the overhead
of indirection (here, extra HTTP requests) but increases total size when
the same data appears in multiple call sites.

## Future direction

The boolean flag is a stepping stone. The observation that there is a
natural size threshold below which inlining is strictly better — where
per-request overhead dominates the cost of any duplicate bytes — points
toward a size-based heuristic, analogous to how compilers choose an
inlining threshold. Small segments would be inlined automatically;
segments exceeding a byte threshold would be "outlined" into separate
requests where deduplication can take effect. For most applications,
this would require no configuration. For applications with specific
latency or bandwidth constraints, an option to adjust the threshold
would let developers tune their position on the requests-vs-bytes curve.
Adaptive heuristics based on network conditions are also possible,
though further out.
2026-03-01 17:10:49 -05:00