What happened
vinext applies a parent layout's title.template to a page that exports a plain string title. Next.js 16 treats a page-level string title as absolute and does not apply the parent template, so the site name ends up duplicated under vinext.
Next.js 16.3.2 Home | Devfive
vinext Home | Devfive | Devfive
Next.js 16.3.2 홈 | 데브파이브
vinext 홈 | 데브파이브 | 데브파이브
Only the two root routes (/en/, /ko/) differ; other routes match, because only these set a page-level string title.
Reproduction
app/[lang]/layout.tsx
export async function generateMetadata(): Promise<Metadata> {
return {
title: {
default: 'Devfive',
template: '%s | Devfive',
},
}
}
app/[lang]/page.tsx
export async function generateMetadata(): Promise<Metadata> {
return { title: 'Home | Devfive' }
}
- Next.js renders
<title>Home | Devfive</title> — the string is absolute, template skipped.
- vinext renders
<title>Home | Devfive | Devfive</title> — template applied on top.
Our application deliberately writes the full title at page level precisely because Next 16 does not apply the template there (we have a code comment saying so, added while fixing an accessibility requirement for unique page titles). Under vinext that workaround double-applies.
Expected
Match Next.js: a page-level title: string is absolute; the parent title.template applies only to title.default resolution for child segments that do not set their own title, and to title: { absolute: ... } / nested title objects per the Metadata API rules.
Why it matters
<title> is user-visible in the tab and in search results, so this is an SEO/UX regression rather than a cosmetic difference. It was caught by a golden-master diff comparing SSR HTML between the two runtimes.
Environment
- vinext 1.0.0-beta.8, Vite 8.2.2
- Next.js 16.3.2 API surface, App Router,
generateMetadata
next-intl for localization (the title strings come from a plain record, not from next-intl, so the plugin is not involved)
--platform=node, output: 'standalone'
What happened
vinext applies a parent layout's
title.templateto a page that exports a plain stringtitle. Next.js 16 treats a page-level string title as absolute and does not apply the parent template, so the site name ends up duplicated under vinext.Only the two root routes (
/en/,/ko/) differ; other routes match, because only these set a page-level string title.Reproduction
app/[lang]/layout.tsxapp/[lang]/page.tsx<title>Home | Devfive</title>— the string is absolute, template skipped.<title>Home | Devfive | Devfive</title>— template applied on top.Our application deliberately writes the full title at page level precisely because Next 16 does not apply the template there (we have a code comment saying so, added while fixing an accessibility requirement for unique page titles). Under vinext that workaround double-applies.
Expected
Match Next.js: a page-level
title: stringis absolute; the parenttitle.templateapplies only totitle.defaultresolution for child segments that do not set their own title, and totitle: { absolute: ... }/ nested title objects per the Metadata API rules.Why it matters
<title>is user-visible in the tab and in search results, so this is an SEO/UX regression rather than a cosmetic difference. It was caught by a golden-master diff comparing SSR HTML between the two runtimes.Environment
generateMetadatanext-intlfor localization (the title strings come from a plain record, not from next-intl, so the plugin is not involved)--platform=node,output: 'standalone'