Skip to content

Conversation

robbchar
Copy link

@robbchar robbchar commented Oct 16, 2025

Fixes #84750

Made Metadata() functions async and await the metadata promise before rendering. This ensures metadata is resolved during initial SSR rather than being deferred via Suspense/Flight data, allowing React's server-side hoisting to correctly place all meta tags in instead of .

Root cause: When metadata goes through Suspense boundaries, it's sent via RSC Flight data after the initial HTML stream has already sent . React cannot hoist backwards in a stream, so tags end up in .

This fix:

  • Forces metadata resolution before streaming begins
  • Metadata becomes part of initial SSR HTML where hoisting works
  • Removes need for @ts-expect-error comments (proper async/await pattern)

Trade-off: Response blocks on metadata resolution (~milliseconds), but this is acceptable since metadata is invisible and correctness (SEO, social sharing) is more critical than micro-optimizations.

(working on updating the tests....)

Fixes vercel#84750

Made Metadata() functions async and await the metadata promise before rendering. This ensures metadata is resolved during initial SSR rather than being deferred via Suspense/Flight data, allowing React's server-side hoisting to correctly place all meta tags in <head> instead of <body>.

Root cause: When metadata goes through Suspense boundaries, it's sent via RSC Flight data after the initial HTML stream has already sent <head>. React cannot
hoist backwards in a stream, so tags end up in <body>.

This fix:
- Forces metadata resolution before streaming begins
- Metadata becomes part of initial SSR HTML where hoisting works
- Removes need for @ts-expect-error comments (proper async/await pattern)

Trade-off: Response blocks on metadata resolution (~milliseconds), but this is acceptable since metadata is invisible and correctness (SEO, social sharing) is more critical than micro-optimizations.
@ijjk
Copy link
Member

ijjk commented Oct 16, 2025

Allow CI Workflow Run

  • approve CI run for commit: 56f09a2

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@ijjk
Copy link
Member

ijjk commented Oct 16, 2025

Allow CI Workflow Run

  • approve CI run for commit: cb105c3

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@robbchar robbchar force-pushed the fix/metadata-head-placement-issue-84750 branch from a13eea7 to 1e900de Compare October 16, 2025 22:43
Updates test expectations to match the fix where metadata now renders in <head> instead of <body>.

Changes:
- metadata-streaming.test.ts: Update all tests to expect metadata in head
- metadata-streaming-static-generation.test.ts: Update dev and dynamic tests

These tests were previously checking that metadata was incorrectly placed in <body>, which was the bug. Now they correctly verify that all metadata tags are in <head> for proper SEO and social sharing.
@ijjk ijjk added the tests label Oct 16, 2025
@robbchar
Copy link
Author

ok updated tests:

  • Tests updated to reflect correct behavior
  • Unable to run e2e tests locally due to SWC binary setup issues
  • Requesting CI approval to validate

…etadata durting the initial SSR phase

### Changes
**Core Fix:**
- `packages/next/src/lib/metadata/metadata.tsx`:
  - Removed conditional logic based on `serveStreamingMetadata`
  - Always use async components that await metadata resolution
  - Removed `Suspense` wrapper from `MetadataOutlet`
  - Added explanatory comment about why metadata must be awaited

**Test Updates:**
Updated tests to expect the new correct behavior (metadata in `<head>`):
- `test/e2e/app-dir/metadata-streaming/metadata-streaming-customized-rule.test.ts`
- `test/e2e/app-dir/metadata-streaming-parallel-routes/metadata-streaming-parallel-routes.test.ts`
- `test/e2e/app-dir/metadata-static-generation/metadata-static-generation.test.ts`
- `test/e2e/app-dir/metadata-icons/metadata-icons.test.ts`

### Trade-offs
- Response may be delayed while metadata resolves (typically <1ms for static metadata)
- Metadata is invisible, so no perceived user impact
- Correctness (SEO, social, HTML validity) > microsecond optimization

### Breaking Changes
- The `htmlLimitedBots` configuration now has reduced effect since all requests get blocking metadata
- Streaming metadata feature is effectively disabled for initial page loads

### References
- Original issue: vercel#84750
- Root cause analysis in ai-docs/FINAL_ROOT_CAUSE_ANALYSIS.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Static metadata (export const metadata) rendered in <body> instead of <head> in 15.5.4

2 participants