Skip to content

Commit aff8678

Browse files
committed
fix(core): render branded page for /404 instead of Vercel default
Visiting /404 directly shows the default Vercel black screen because Next.js special-cases this path with a pre-rendered static 404.html, bypassing the [locale] segment entirely. Fix by splitting the layout into a true root layout (app/layout.tsx) owning <html>, <body>, fonts, and globals.css, and converting the locale layout into a nested layout. A new root not-found.tsx renders a minimal branded 404 page for this edge case. Made-with: Cursor
1 parent 8b5fee6 commit aff8678

4 files changed

Lines changed: 61 additions & 32 deletions

File tree

.changeset/fix-root-not-found.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@bigcommerce/catalyst-core": patch
3+
---
4+
5+
Add root-level not-found page so /404 renders a branded page instead of the default Vercel error screen

core/app/[locale]/layout.tsx

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
import { Analytics } from '@vercel/analytics/react';
22
import { SpeedInsights } from '@vercel/speed-insights/next';
3-
import { clsx } from 'clsx';
43
import type { Metadata } from 'next';
54
import { notFound } from 'next/navigation';
65
import { NextIntlClientProvider } from 'next-intl';
76
import { setRequestLocale } from 'next-intl/server';
87
import { NuqsAdapter } from 'nuqs/adapters/next/app';
98
import { cache, PropsWithChildren } from 'react';
109

11-
import '../../globals.css';
12-
13-
import { fonts } from '~/app/fonts';
1410
import { CookieNotifications } from '~/app/notifications';
1511
import { Providers } from '~/app/providers';
1612
import { client } from '~/client';
@@ -140,34 +136,32 @@ export default async function RootLayout({ params, children }: Props) {
140136
const privacyPolicyUrl = rootData.data.site.settings?.privacy?.privacyPolicyUrl;
141137

142138
return (
143-
<html className={clsx(fonts.map((f) => f.variable))} lang={locale}>
144-
<body className="flex min-h-screen flex-col">
145-
<NextIntlClientProvider>
146-
<ConsentManager
147-
isCookieConsentEnabled={isCookieConsentEnabled}
148-
privacyPolicyUrl={privacyPolicyUrl}
149-
scripts={scripts}
150-
>
151-
<NuqsAdapter>
152-
<AnalyticsProvider
153-
channelId={rootData.data.channel.entityId}
154-
isCookieConsentEnabled={isCookieConsentEnabled}
155-
settings={rootData.data.site.settings}
156-
>
157-
<Providers>
158-
{toastNotificationCookieData && (
159-
<CookieNotifications {...toastNotificationCookieData} />
160-
)}
161-
{children}
162-
</Providers>
163-
</AnalyticsProvider>
164-
</NuqsAdapter>
165-
</ConsentManager>
166-
</NextIntlClientProvider>
167-
<VercelComponents />
168-
<ContainerQueryPolyfill />
169-
</body>
170-
</html>
139+
<>
140+
<NextIntlClientProvider>
141+
<ConsentManager
142+
isCookieConsentEnabled={isCookieConsentEnabled}
143+
privacyPolicyUrl={privacyPolicyUrl}
144+
scripts={scripts}
145+
>
146+
<NuqsAdapter>
147+
<AnalyticsProvider
148+
channelId={rootData.data.channel.entityId}
149+
isCookieConsentEnabled={isCookieConsentEnabled}
150+
settings={rootData.data.site.settings}
151+
>
152+
<Providers>
153+
{toastNotificationCookieData && (
154+
<CookieNotifications {...toastNotificationCookieData} />
155+
)}
156+
{children}
157+
</Providers>
158+
</AnalyticsProvider>
159+
</NuqsAdapter>
160+
</ConsentManager>
161+
</NextIntlClientProvider>
162+
<VercelComponents />
163+
<ContainerQueryPolyfill />
164+
</>
171165
);
172166
}
173167

core/app/layout.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { clsx } from 'clsx';
2+
import { PropsWithChildren } from 'react';
3+
4+
import '../globals.css';
5+
6+
import { fonts } from '~/app/fonts';
7+
8+
export default function RootLayout({ children }: PropsWithChildren) {
9+
return (
10+
<html className={clsx(fonts.map((f) => f.variable))} lang="en">
11+
<body className="flex min-h-screen flex-col">{children}</body>
12+
</html>
13+
);
14+
}

core/app/not-found.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export default function RootNotFound() {
2+
return (
3+
<div className="flex min-h-screen flex-col items-center justify-center bg-[hsl(var(--background))] text-[hsl(var(--foreground))]">
4+
<h1 className="font-heading text-4xl font-medium">Page not found</h1>
5+
<p className="mt-4 text-lg text-[hsl(var(--contrast-400))]">
6+
The page you are looking for could not be found.
7+
</p>
8+
<a
9+
className="mt-6 inline-flex items-center text-[hsl(var(--foreground))] underline underline-offset-4 hover:no-underline"
10+
href="/"
11+
>
12+
Go to homepage
13+
</a>
14+
</div>
15+
);
16+
}

0 commit comments

Comments
 (0)