Skip to content

fix(core): restore locale-aware lang attribute on html#2989

Merged
jorgemoya merged 1 commit intocanaryfrom
jorgemoya/ltrac-578-pages-always-serve-langen-regardless-of-locale
Apr 24, 2026
Merged

fix(core): restore locale-aware lang attribute on html#2989
jorgemoya merged 1 commit intocanaryfrom
jorgemoya/ltrac-578-pages-always-serve-langen-regardless-of-locale

Conversation

@jorgemoya
Copy link
Copy Markdown
Contributor

@jorgemoya jorgemoya commented Apr 23, 2026

Linear: LTRAC-578

What/Why?

PR #2947 introduced a root app/layout.tsx that owns <html>/<body> so /404 renders a branded page instead of the default Vercel error screen. Because that root layout sits above [locale], the lang attribute was hardcoded to lang="en" for every locale — regressing accessibility and SEO for non-English storefronts.

Full rootParams (Next.js 16.2) would be the clean fix but isn't usable on Native Hosting yet. This PR adopts next-intl's documented workaround instead:

  • app/layout.tsx → passthrough (return children). Next.js still requires the file to exist, but it owns no markup.
  • app/[locale]/layout.tsx → owns <html lang={locale}> + <body> + fonts + globals.css again, so each locale gets its correct language attribute.
  • app/not-found.tsx → self-sufficient, renders its own <html lang="en">/<body> + fonts + globals.css. Keeps the branded 404 from fix(core): adds root-level not found page #2947 working for non-localized requests.

Why a passthrough and not deleting the root layout: deleting it forces Next.js 16 to synthesize a fallback shell, which produces nested <html>/<body> tags in the prerendered _not-found.html. A passthrough tells Next.js "trust the children" and avoids the synthesized shell.

Testing

  • pnpm build succeeds. Prerendered .next/server/app/_not-found.html contains a single <html lang="en" class="...fonts..."> and a single <body class="flex min-h-screen flex-col"> wrapping the branded "Not found" content.
  • pnpm start:
    • GET / → 200, <html lang="en" class="...fonts...">
    • Any /[locale]/... route will render with lang={locale} (ticket fix)
    • GET /admin/nonexistent/ → 404, renders branded not-found. Runtime serves Next.js's <html id="__next_error__"> streaming shell; verified this is identical on canary — baseline Next.js 16 behavior for dynamic 404s, not a regression from this PR. On Vercel edge, the prerendered static file is served directly.

Migration

  • Customers that customized the root app/layout.tsx (e.g., added <head> elements, body classes, providers) need to move those customizations up to app/[locale]/layout.tsx (for locale-aware pages) and/or app/not-found.tsx (for the non-localized fallback).
  • If a custom root layout was added since fix(core): adds root-level not found page #2947 to set <html lang> via a cookie or similar workaround, it can be deleted — the locale layout handles this now.

Fixes LTRAC-578

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 23, 2026

🦋 Changeset detected

Latest commit: ec8034b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@bigcommerce/catalyst-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
catalyst Ready Ready Preview, Comment Apr 24, 2026 3:38pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 23, 2026

Bundle Size Report

Comparing against baseline from 225c4ef (2026-04-24).

No bundle size changes detected.

@jorgemoya jorgemoya marked this pull request as ready for review April 23, 2026 20:19
@jorgemoya jorgemoya requested a review from a team as a code owner April 23, 2026 20:19
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 23, 2026

Unlighthouse Performance Comparison — Vercel

Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.

Summary Score

Aggregate score across all categories as reported by Unlighthouse.

Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Score 89 93 91 95

Category Scores

Category Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Performance 73 82 76 91
Accessibility 95 95 95 98
Best Practices 100 100 95 100
SEO 88 88 100 100

Core Web Vitals

Metric Prod Desktop Prod Mobile Preview Desktop Preview Mobile
LCP 5.0 s 4.9 s 3.8 s 3.5 s
CLS 0.037 0 0.037 0.009
FCP 1.2 s 1.2 s 1.2 s 1.2 s
TBT 0 ms 0 ms 10 ms 20 ms
Max Potential FID 50 ms 40 ms 60 ms 90 ms
Time to Interactive 5.0 s 4.9 s 4.0 s 3.9 s

Full Unlighthouse report →

Copy link
Copy Markdown
Contributor

@chanceaclark chanceaclark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind just adding a TODO in code for use to move this back using rootParams when we have it available?

@jorgemoya jorgemoya changed the title LTRAC-578: fix(core) - Restore locale-aware lang attribute on html fix(core) - Restore locale-aware lang attribute on html Apr 23, 2026
@migueloller
Copy link
Copy Markdown
Contributor

@jorgemoya, minor nit but the Jira link in the PR description actually points to a Linear issue.

@jorgemoya jorgemoya force-pushed the jorgemoya/ltrac-578-pages-always-serve-langen-regardless-of-locale branch from e2050fc to b258aed Compare April 24, 2026 15:07
@jorgemoya jorgemoya force-pushed the jorgemoya/ltrac-578-pages-always-serve-langen-regardless-of-locale branch from b258aed to 0966b72 Compare April 24, 2026 15:35
@jorgemoya jorgemoya changed the title fix(core) - Restore locale-aware lang attribute on html fix(core): restore locale-aware lang attribute on html Apr 24, 2026
The root <html> tag was hardcoded to lang="en" for all locales because
a new root app/layout.tsx to enable the branded /404 page. That fix
solved the 404 but regressed the lang attribute.

Adopt next-intl's documented pattern:
- app/layout.tsx is now a passthrough (returns children) — required by
  Next.js but owns no markup.
- app/[locale]/layout.tsx owns <html lang={locale}> and <body> again,
  so localized pages get the correct language.
- app/not-found.tsx owns its own <html>/<body>, fonts, and global CSS
  so the branded 404 still renders for non-localized requests.

Verified parity with canary on runtime 404 behavior (both serve the
Next.js error-fallback shell for dynamic 404s) and clean output in the
prerendered _not-found.html static file.

Fixes LTRAC-578

Co-Authored-By: Claude <noreply@anthropic.com>

LTRAC-578: chore - Bump changeset to minor

Restructures <html>/<body> ownership across root layout, [locale]
layout, and not-found — customers who customized these files will need
to migrate, so a minor bump is more appropriate than patch.

Refs LTRAC-578

Co-Authored-By: Claude <noreply@anthropic.com>

LTRAC-578: docs - Add TODO for rootParams migration

Note that the passthrough root layout is a workaround — once
Next.js `rootParams` (16.2+) is available on Native Hosting, we
should move <html>/<body> back here and derive `lang` from rootParams.

Refs LTRAC-578

Co-Authored-By: Claude <noreply@anthropic.com>
@jorgemoya jorgemoya force-pushed the jorgemoya/ltrac-578-pages-always-serve-langen-regardless-of-locale branch from 0966b72 to ec8034b Compare April 24, 2026 15:38
@jorgemoya jorgemoya enabled auto-merge April 24, 2026 15:38
@jorgemoya jorgemoya added this pull request to the merge queue Apr 24, 2026
Merged via the queue into canary with commit 518d20a Apr 24, 2026
16 of 17 checks passed
@jorgemoya jorgemoya deleted the jorgemoya/ltrac-578-pages-always-serve-langen-regardless-of-locale branch April 24, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants