[PR #891] [MERGED] feat(app-router): feed file metadata routes into head output #923

Closed
opened 2026-05-06 13:10:51 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/891
Author: @NathanDrake2406
Created: 4/25/2026
Status: Merged
Merged: 4/30/2026
Merged by: @james-elicx

Base: mainHead: nathanfix-static-metadata-parity


📝 Commits (1)

  • d9d8d77 feat(app-router): feed file metadata routes into head output

📊 Changes

55 files changed (+5277 additions, -949 deletions)

View changed files

📝 knip.ts (+2 -0)
📝 package.json (+0 -1)
📝 packages/vinext/package.json (+1 -0)
📝 packages/vinext/src/entries/app-rsc-entry.ts (+41 -97)
📝 packages/vinext/src/entries/app-rsc-manifest.ts (+4 -55)
📝 packages/vinext/src/server/app-browser-entry.ts (+35 -1)
📝 packages/vinext/src/server/app-page-boundary-render.ts (+76 -6)
📝 packages/vinext/src/server/app-page-cache.ts (+21 -0)
📝 packages/vinext/src/server/app-page-head.ts (+334 -67)
📝 packages/vinext/src/server/app-prerender-endpoints.ts (+23 -5)
packages/vinext/src/server/app-prerender-static-params.ts (+21 -0)
packages/vinext/src/server/file-based-metadata.ts (+842 -0)
packages/vinext/src/server/metadata-route-build-data.ts (+264 -0)
packages/vinext/src/server/metadata-route-response.ts (+374 -0)
📝 packages/vinext/src/server/metadata-routes.ts (+210 -32)
📝 packages/vinext/src/shims/cache.ts (+1 -1)
📝 packages/vinext/src/shims/metadata.tsx (+162 -45)
packages/vinext/src/shims/thenable-params.ts (+41 -0)
📝 pnpm-lock.yaml (+3 -3)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+202 -577)

...and 35 more files

📄 Description

What this changes

Fixes #884 by folding App Router file-convention metadata into rendered head output and by serving the corresponding static and dynamic metadata assets with the route metadata needed by the head renderer.

This covers static icon/apple/Open Graph/Twitter/manifest files, static social image .alt.txt files, content-hashed injected URLs, image dimensions and content types, placeholder URLs for static metadata under dynamic segments, numbered dynamic image route variants such as opengraph-image2.tsx, and generateImageMetadata() id routes.

Follow-up review fixes in this branch also keep metadata route generation thin, add path-specific read failures for scanned static metadata, preserve URL-valued metadata without deep cloning, scope layout metadata params to the layout segment, include active and intercepted parallel route metadata in head resolution, preserve static manifest.json URLs, avoid parsing image dimensions for non-image metadata files, align generateSitemaps() id handling with Next, and seed root params around prerender generateStaticParams().

Why

vinext could serve metadata files as routes, but those files were not consistently reflected in metadata.icons, metadata.openGraph.images, metadata.twitter.images, or metadata.manifest before MetadataHead rendered. That meant browsers and crawlers could miss file-convention metadata even when the asset URL itself existed.

The failure mode was also too quiet for static metadata files: if a scanned file could not be read, bad state could travel into missing hashes or empty metadata responses. Static file read failures now fail during entry generation with the specific file path.

Approach

  • collect static and dynamic metadata route head data during RSC entry generation while delegating runtime behavior to typed server helpers
  • fail entry generation with a path-specific diagnostic if a scanned static metadata file or static social alt file cannot be read
  • apply file metadata with segment-scoped precedence that matches observed Next.js behavior: same-segment explicit social images/icons win, inherited parent social images do not block a leaf file social image, and inherited explicit icons suppress leaf file icons
  • keep raw app tree route segments on metadata routes so route groups and non-children parallel routes do not collapse to visible URL prefix only
  • resolve page head outside the generated RSC entry, including layout-scoped params plus active/intercepted parallel route metadata
  • keep codegen as app-shape wiring: active slot selection and prerender root-param seeding live in typed server helpers
  • avoid deep-cloning metadata before mutation so URL values such as metadataBase survive file metadata application
  • include static opengraph-image.alt.txt and twitter-image.alt.txt content in rendered OG/Twitter image alt metadata
  • preserve static manifest.json URLs and serve static manifests with application/manifest+json
  • serve generateImageMetadata() id routes and validate generated ids before calling the selected image handler
  • pass generated sitemap ids as promised URL string ids and throw on missing generateSitemaps() ids, matching Next

Non-goals

  • broader metadataBase or basePath parity beyond the file-based metadata paths touched here
  • changing dynamic metadata image route paths away from vinext current unsuffixed convention when generateImageMetadata() is not used
  • a full App Router loader-tree rewrite; this keeps generated entry wiring thin and moves testable behavior into server helpers

Validation

  • vp check
  • vp test run tests/app-page-head.test.ts tests/file-based-metadata.test.ts tests/metadata-routes.test.ts tests/metadata-route-response.test.ts tests/entry-templates.test.ts tests/app-page-boundary-render.test.ts
  • vp test run tests/app-router.test.ts tests/metadata-route-response.test.ts
  • PR CI passed: Check, Vitest unit/integration, create-next-app, preview publish, and all E2E jobs

Next.js sources referenced

  • packages/next/src/lib/metadata/resolve-metadata.ts
  • packages/next/src/build/webpack/loaders/next-metadata-image-loader.ts
  • packages/next/src/build/webpack/loaders/next-metadata-route-loader.ts
  • packages/next/src/lib/metadata/get-metadata-route.ts
  • packages/next/src/lib/metadata/is-metadata-route.ts
  • test/e2e/app-dir/metadata/metadata.test.ts
  • test/e2e/app-dir/metadata-dynamic-routes/index.test.ts
  • test/e2e/app-dir/metadata-static-file/

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/891 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 4/25/2026 **Status:** ✅ Merged **Merged:** 4/30/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `nathanfix-static-metadata-parity` --- ### 📝 Commits (1) - [`d9d8d77`](https://github.com/cloudflare/vinext/commit/d9d8d774a0554915d3b4a97bdd30eeb405fc2fdc) feat(app-router): feed file metadata routes into head output ### 📊 Changes **55 files changed** (+5277 additions, -949 deletions) <details> <summary>View changed files</summary> 📝 `knip.ts` (+2 -0) 📝 `package.json` (+0 -1) 📝 `packages/vinext/package.json` (+1 -0) 📝 `packages/vinext/src/entries/app-rsc-entry.ts` (+41 -97) 📝 `packages/vinext/src/entries/app-rsc-manifest.ts` (+4 -55) 📝 `packages/vinext/src/server/app-browser-entry.ts` (+35 -1) 📝 `packages/vinext/src/server/app-page-boundary-render.ts` (+76 -6) 📝 `packages/vinext/src/server/app-page-cache.ts` (+21 -0) 📝 `packages/vinext/src/server/app-page-head.ts` (+334 -67) 📝 `packages/vinext/src/server/app-prerender-endpoints.ts` (+23 -5) ➕ `packages/vinext/src/server/app-prerender-static-params.ts` (+21 -0) ➕ `packages/vinext/src/server/file-based-metadata.ts` (+842 -0) ➕ `packages/vinext/src/server/metadata-route-build-data.ts` (+264 -0) ➕ `packages/vinext/src/server/metadata-route-response.ts` (+374 -0) 📝 `packages/vinext/src/server/metadata-routes.ts` (+210 -32) 📝 `packages/vinext/src/shims/cache.ts` (+1 -1) 📝 `packages/vinext/src/shims/metadata.tsx` (+162 -45) ➕ `packages/vinext/src/shims/thenable-params.ts` (+41 -0) 📝 `pnpm-lock.yaml` (+3 -3) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+202 -577) _...and 35 more files_ </details> ### 📄 Description ## What this changes Fixes #884 by folding App Router file-convention metadata into rendered head output and by serving the corresponding static and dynamic metadata assets with the route metadata needed by the head renderer. This covers static icon/apple/Open Graph/Twitter/manifest files, static social image `.alt.txt` files, content-hashed injected URLs, image dimensions and content types, placeholder URLs for static metadata under dynamic segments, numbered dynamic image route variants such as `opengraph-image2.tsx`, and `generateImageMetadata()` id routes. Follow-up review fixes in this branch also keep metadata route generation thin, add path-specific read failures for scanned static metadata, preserve URL-valued metadata without deep cloning, scope layout metadata params to the layout segment, include active and intercepted parallel route metadata in head resolution, preserve static `manifest.json` URLs, avoid parsing image dimensions for non-image metadata files, align `generateSitemaps()` id handling with Next, and seed root params around prerender `generateStaticParams()`. ## Why vinext could serve metadata files as routes, but those files were not consistently reflected in `metadata.icons`, `metadata.openGraph.images`, `metadata.twitter.images`, or `metadata.manifest` before `MetadataHead` rendered. That meant browsers and crawlers could miss file-convention metadata even when the asset URL itself existed. The failure mode was also too quiet for static metadata files: if a scanned file could not be read, bad state could travel into missing hashes or empty metadata responses. Static file read failures now fail during entry generation with the specific file path. ## Approach - collect static and dynamic metadata route head data during RSC entry generation while delegating runtime behavior to typed server helpers - fail entry generation with a path-specific diagnostic if a scanned static metadata file or static social alt file cannot be read - apply file metadata with segment-scoped precedence that matches observed Next.js behavior: same-segment explicit social images/icons win, inherited parent social images do not block a leaf file social image, and inherited explicit icons suppress leaf file icons - keep raw app tree route segments on metadata routes so route groups and non-children parallel routes do not collapse to visible URL prefix only - resolve page head outside the generated RSC entry, including layout-scoped params plus active/intercepted parallel route metadata - keep codegen as app-shape wiring: active slot selection and prerender root-param seeding live in typed server helpers - avoid deep-cloning metadata before mutation so `URL` values such as `metadataBase` survive file metadata application - include static `opengraph-image.alt.txt` and `twitter-image.alt.txt` content in rendered OG/Twitter image alt metadata - preserve static `manifest.json` URLs and serve static manifests with `application/manifest+json` - serve `generateImageMetadata()` id routes and validate generated ids before calling the selected image handler - pass generated sitemap ids as promised URL string ids and throw on missing `generateSitemaps()` ids, matching Next ## Non-goals - broader `metadataBase` or `basePath` parity beyond the file-based metadata paths touched here - changing dynamic metadata image route paths away from vinext current unsuffixed convention when `generateImageMetadata()` is not used - a full App Router loader-tree rewrite; this keeps generated entry wiring thin and moves testable behavior into server helpers ## Validation - `vp check` - `vp test run tests/app-page-head.test.ts tests/file-based-metadata.test.ts tests/metadata-routes.test.ts tests/metadata-route-response.test.ts tests/entry-templates.test.ts tests/app-page-boundary-render.test.ts` - `vp test run tests/app-router.test.ts tests/metadata-route-response.test.ts` - PR CI passed: Check, Vitest unit/integration, create-next-app, preview publish, and all E2E jobs ## Next.js sources referenced - `packages/next/src/lib/metadata/resolve-metadata.ts` - `packages/next/src/build/webpack/loaders/next-metadata-image-loader.ts` - `packages/next/src/build/webpack/loaders/next-metadata-route-loader.ts` - `packages/next/src/lib/metadata/get-metadata-route.ts` - `packages/next/src/lib/metadata/is-metadata-route.ts` - `test/e2e/app-dir/metadata/metadata.test.ts` - `test/e2e/app-dir/metadata-dynamic-routes/index.test.ts` - `test/e2e/app-dir/metadata-static-file/` --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:10:51 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#923
No description provided.