Commit Graph

8 Commits

Author SHA1 Message Date
James Anderson fbf68d588e fix(app-router): restore cache and parallel route parity (#2261)
* fix(app-router): preserve searchParams cache variants

* test(app-router): cover RSC metadata cache variants

* fix(app-router): isolate metadata searchParams observation

* fix(app-router): preserve static ISR cache proofs

* fix(app-router): limit queryless cache probes

* fix(app-router): skip redundant cache proof probes

* fix(app-router): harden incomplete cache proofs

* fix(app-router): observe loading page execution

* fix(app-router): preserve exotic page exports

* fix(app-router): preserve client page ISR

* fix(app-router): avoid duplicate queryless page execution

* fix(app-router): observe viewport search params

* fix(app-router): clear force-static search params

* fix(app-router): isolate intercept static context

* fix(app-router): preserve intercept request context

* fix(app-router): isolate action static context

* fix(app-router): match static request API semantics

* fix(app-router): preserve dynamic segment constraints

* fix(app-router): apply slot-only segment config

* fix(app-router): preserve draft mode reads in static errors

* fix(app-router): include nested slot constraints

* fix(app-router): refresh slot layout constraints

* fix(app-router): render nested slot layouts

* fix(app-router): preserve nested slot params

* fix(app-router): preserve intercepted slot head

* fix(app-router): scope parallel layout params

* fix(app-router): preserve intercept layout parity

* fix(app-router): preserve intercept ancestor params

* fix(app-router): scope intercept source params

* fix(app-router): preserve intercept source route params

* test(app-router): stabilize parity validation
2026-06-22 23:17:41 +01:00
James Anderson 1047cccd8a fix(metadata): omit unused parent arg for cached generateMetadata (#1719)
* fix(metadata): omit unused parent arg for cached generateMetadata

A module-level `'use cache'` directive wraps `generateMetadata` in the cache
runtime. vinext always called it as `generateMetadata(props, parent)`, where
`parent` resolves to the merged ancestor metadata. When an ancestor sets
`metadataBase: new URL(...)`, the cache-key encoder (`encodeReply`) awaited the
parent promise and tried to serialize the `URL` instance, logging React's
"Only plain objects can be passed to Server Functions from the Client. URL
objects are not supported." warning.

Match Next.js, which omits the `parent` argument for `generateMetadata`
functions that don't use it (resolve-metadata.ts `getResult` /
`useCacheFunctionInfo.usedArgs[1]`):

- `resolveModuleMetadata` now only passes `parent` when `generateMetadata`
  declares it (`fn.length >= 2`).
- `registerCachedFunction` preserves the original function's arity on the
  wrapper so the arity check works for cached functions.

Adds regression tests for the arity gating, metadataBase preservation, and
arity-preserving wrapper.

* docs(metadata): note arity approximates parent usage; drop dead try/catch

Address /bigbonk review feedback:
- Document that fn.length approximates Next.js's usedArgs[1] analysis and
  diverges on default-parameter signatures.
- Remove the dead try/catch around Object.defineProperty (function length is
  always configurable per spec).
2026-06-02 11:50:17 +01:00
Nathan Nguyen 4b6b438b94 fix(app-router): stream generated metadata for non-html bots (#1585)
* fix(app-router): stream generated metadata for non-html bots

Dynamic App Router metadata was always folded into the route head. That diverged from Next.js for normal browser-like requests, where generated metadata is sent through a body outlet while html-limited bots still receive blocking head metadata.

Track whether the matched route uses generateMetadata, thread next.config htmlLimitedBots into the generated RSC entry, and choose head versus body placement at page element construction.

* test(app-router): cover streaming metadata bot edge cases

Generated metadata must keep the default html-limited bot list when serialized config contains a falsy regex source. Empty config strings previously produced an empty regex, which treated every user agent as blocking and moved generated metadata back into the head.

Add regression coverage for the default Twitterbot path and the streaming body serializer, then match Next.js by falling back to the default bot regex for any falsy htmlLimitedBots value.

* fix(app-router): validate html-limited bot regex config

Invalid serialized htmlLimitedBots values could reach request handling and throw while building the bot matcher. That puts a config error on the request path and recompiles the same matcher for repeated requests.

Validate serialized regex sources while resolving next.config and reuse compiled bot matchers in the streaming metadata helper. The focused tests cover invalid config, falsy fallback, and matcher reuse.

* refactor(config): narrow next config option reads

Config resolution had several local assertions around experimental options, Turbopack aliases, output mode, and Sass options. Those assertions made the boundary wider than necessary in the code adjacent to htmlLimitedBots handling.

Read optional records, strings, arrays, and body-size inputs through typed narrowing helpers so the resolved config path keeps the same semantics without spreading unchecked asserted values.

* fix(config): keep bot regex helper out of server graph

Config resolution imported the streaming metadata server module to validate htmlLimitedBots. In RSC-backed App Router integration tests, that cross-layer import can pull server runtime modules through config resolution and surface rsc reference-validation errors.

Move the shared html-limited bot matcher cache to a neutral utils module and import it from config and the server streaming metadata helper. This keeps config validation independent from the server runtime graph while preserving the cached matcher behavior.
2026-05-26 12:07:07 +01:00
James Anderson 5d397d5f4b fix(metadata): emit metadata from parallel route slots (#1399)
When the route has no children page providing a title (parallel layout
that doesn't render `{children}`, or a parent that only has
`default.tsx`), allow the active parallel slot page to contribute the
primary <title>. Previously every slot was forced with
`contributesTitle: false`, which suppressed the slot title even when no
primary page title existed — producing an empty title or stale layout
title for these routes.

Slots keep the existing suppression when the matched page module already
provides a title, preserving the modal/sidebar case where the slot
metadata must not clobber the page title.

Refs #1341
2026-05-21 10:28:22 +01:00
Nathan Nguyen b69844ec3f fix(metadata): apply ancestor templates to title defaults (#1256)
Metadata title defaults currently render without the active ancestor template. That diverges from Next.js when a child layout or page provides title.default under a parent layout title.template.

The merge path resolved only the final title after collecting templates, so object defaults skipped the stashed template used for that segment. Resolve each title as it is encountered against the current ancestor template, then stash the current layout template for descendants.

Covers the child-layout default regression and updates page-default expectations to match Next.js resolveTitle semantics.
2026-05-16 18:08:40 +01:00
Nathan Nguyen 2bd7377f70 fix(shims): match Next metadata merge and Twitter inheritance (#1055)
* fix(shims): deep-merge nested metadata and add OG/Twitter inheritance

mergeMetadataEntries did a flat top-level key replacement, so setting
openGraph: { title: "Override" } in a child layout erased all root
OpenGraph fields like siteName and images. Next.js resolves these
per-subkey; we now shallow-merge the nested objects for openGraph,
twitter, icons, alternates, robots, and other.

Setting only openGraph.title without twitter.title produced no Twitter
Card tags because vinext skipped Next.js's postProcessMetadata step.
We now auto-fill twitter:title from og:title (or metadata:title),
twitter:description from og:description (or metadata:description),
and twitter:images from og:images, and default twitter:card to
summary_large_image when images are present.

Ported from Next.js:
- https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/metadata/resolve-metadata.ts
- https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts

* fixup! fix(shims): deep-merge nested metadata and add OG/Twitter inheritance

Extract postProcessMetadata from mergeMetadataEntries to avoid
polluting intermediate merge results during layout accumulation.
Run it once at the end of resolveAppPageHead after file-based
metadata has been applied, matching Next.js's accumulator pattern.

Also fix two issues caught by Copilot review and CI:

1. Twitter auto-fill now works even when openGraph is absent —
   metadata.title and metadata.description serve as fallback sources.

2. Remove all as assertions from production code; use typed locals
   and direct index access via Metadata's string index signature.

3. Update app-page-head.test.ts expectations to match post-processed
   output and add missing regression test for no-openGraph case.

* chore: address review feedback — clone input, ?? for title, missing test

- postProcessMetadata now shallow-clones its input to prevent accidental
  mutation of the caller's object.
- resolveStringTitle uses ?? instead of || to avoid treating empty
  string as falsy (semantically correct but practically no difference).
- Added comment explaining why verification, appleWebApp, appLinks,
  and formatDetection are excluded from DEEP_MERGE_KEYS.
- Added test: child can clear a parent openGraph subkey by setting it
  to undefined, verifying twitter auto-fill skips cleared images.

* fix(shims): match Next metadata segment replacement

Nested metadata objects were being merged across segments, which kept stale parent OpenGraph, Twitter, alternates, icons, and robots fields when a child segment defined the same key.

Next only merges custom other metadata across segments. Replace the broad nested merge with an explicit other merge, keep OG-to-Twitter post-processing gated by openGraph, and update metadata tests to lock in the compatibility contract.
2026-05-05 19:00:10 +01:00
Nathan Nguyen 1d7c031e30 feat(app-router): feed file metadata routes into head output (#891) 2026-04-30 19:38:32 +01:00
Nathan Nguyen dd1dfae3c6 fix(app-router): scope layout params and layout error boundaries (#938)
* fix(app-router): scope layout params and propagate layout head errors

Layouts received full route params, layout generateMetadata failures were swallowed, and layout-thrown forbidden()/unauthorized() fell back through the not-found boundary path. Those diverge from Next.js when nested layouts depend on segment params, when head generation fails before page render, or when HTTP access APIs are thrown from a layout.

The generated RSC entry now delegates param slicing, head resolution, and parent access-boundary selection to typed runtime helpers. The helpers keep route-specific imports in the entry while moving the behavioral core into unit-tested modules.

Tests port Next.js layout params, global-error, forbidden, and unauthorized coverage into focused helper and integration regressions.

* test(app-router): cover layout viewport error boundaries

Layout generateViewport() failures now intentionally share the same boundary path as layout generateMetadata() failures. Copilot flagged that this behavior change needed direct coverage.

Add dev and production-preview compat assertions for co-located error.tsx handling and global-error escalation, with fixtures that throw from layout generateViewport().

* chore(app-router): address head review nits
2026-04-29 10:40:49 +01:00