mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[GH-ISSUE #880] App Router: root layout html/body wrapped again by outer SSR, creating nested tags #195
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#195
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @eduardornj on GitHub (Apr 24, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/880
Description
When the App Router root layout at
app/[locale]/layout.tsxreturns<html>...<body>...</body></html>(as Next.js docs require), the rendered HTML in production ends up with everything nested inside another<html><body>wrapper:Two
<html>, three<head>, two<body>in the final output. Tested on 0.0.43, 0.0.42, 0.0.41, 0.0.40.Why this matters
Google Rich Results Test parses the inner
<html>as a separate document, so any<script type="application/ld+json">placed in the inner<head>gets counted twice. My site has a singleLocalBusinessschema withaggregateRatingin the root layout. Validator reports "Review has multiple aggregate ratings" because it sees twoLocalBusinessobjects sharing the same@id, and marks the rich result as invalid.Steps to Reproduce
<html>and<body>tags<script type="application/ld+json">inside<head>containingLocalBusinesswithaggregateRatingnpx vinext build && wrangler deployValidator flags "multiple aggregate ratings" and the schema is rejected.
Quick check on any Vinext site in prod
Workaround I landed on
Removed
<html>,<head>,<body>from the root layout, returned a<>fragment, let React 19 hoist<link>/<meta>/<script>to the document head. Wrapped the rest in<div className="min-h-screen flex flex-col">to replace body-level flex.Works (Rich Results now validates with 0 errors), but breaks the Next.js App Router contract which explicitly says the root layout must include
<html>and<body>. Will need to revert once Vinext stops double-wrapping.Happy to put together a minimal repro if it helps narrow this down.
@NathanDrake2406 commented on GitHub (Apr 25, 2026):
I can't repro this, could you add a repro?