Skip to content

Commit 94d2aaa

Browse files
authored
Merge pull request #15584 from ethereum/500-404-patches
fix: Error (500) and Not Found (404) routing logic
2 parents deb5e69 + f9262dc commit 94d2aaa

File tree

9 files changed

+193
-104
lines changed

9 files changed

+193
-104
lines changed

app/[locale]/[...slug]/page.tsx

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import { pick } from "lodash"
22
import { notFound } from "next/navigation"
3-
import { getMessages, setRequestLocale } from "next-intl/server"
3+
import {
4+
getMessages,
5+
getTranslations,
6+
setRequestLocale,
7+
} from "next-intl/server"
8+
9+
import { SlugPageParams } from "@/lib/types"
410

511
import I18nProvider from "@/components/I18nProvider"
612
import mdComponents from "@/components/MdComponents"
713

814
import { dataLoader } from "@/lib/utils/data/dataLoader"
915
import { dateToString } from "@/lib/utils/date"
1016
import { getLayoutFromSlug } from "@/lib/utils/layout"
11-
import { getPostSlugs } from "@/lib/utils/md"
17+
import { checkPathValidity, getPostSlugs } from "@/lib/utils/md"
1218
import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
1319

1420
import { LOCALES_CODES } from "@/lib/constants"
@@ -23,20 +29,15 @@ const loadData = dataLoader([["gfissues", fetchGFIs]])
2329
export default async function Page({
2430
params,
2531
}: {
26-
params: Promise<{ locale: string; slug: string[] }>
32+
params: Promise<SlugPageParams>
2733
}) {
2834
const { locale, slug: slugArray } = await params
2935

3036
// Check if this specific path is in our valid paths
3137
const validPaths = await generateStaticParams()
32-
const isValidPath = validPaths.some(
33-
(path) =>
34-
path.locale === locale && path.slug.join("/") === slugArray.join("/")
35-
)
38+
const isValidPath = checkPathValidity(validPaths, await params)
3639

37-
if (!isValidPath) {
38-
notFound()
39-
}
40+
if (!isValidPath) notFound()
4041

4142
// Enable static rendering
4243
setRequestLocale(locale)
@@ -97,27 +98,45 @@ export default async function Page({
9798
}
9899

99100
export async function generateStaticParams() {
100-
const slugs = await getPostSlugs("/")
101-
102-
return LOCALES_CODES.flatMap((locale) =>
103-
slugs.map((slug) => ({
104-
slug: slug.split("/").slice(1),
105-
locale,
106-
}))
107-
)
101+
try {
102+
const slugs = await getPostSlugs("/")
103+
104+
return LOCALES_CODES.flatMap((locale) =>
105+
slugs.map((slug) => ({
106+
slug: slug.split("/").slice(1),
107+
locale,
108+
}))
109+
)
110+
} catch (error) {
111+
// If content directory doesn't exist (e.g., in Netlify serverless environment),
112+
// return empty array to allow ISR to handle all routes dynamically
113+
console.warn(
114+
"Content directory not found, enabling full dynamic routing:",
115+
error
116+
)
117+
return []
118+
}
108119
}
109120

110-
export const dynamicParams = false
111-
112121
export async function generateMetadata({
113122
params,
114123
}: {
115-
params: Promise<{ locale: string; slug: string[] }>
124+
params: Promise<SlugPageParams>
116125
}) {
117126
const { locale, slug } = await params
118127

119-
return await getMdMetadata({
120-
locale,
121-
slug,
122-
})
128+
try {
129+
return await getMdMetadata({
130+
locale,
131+
slug,
132+
})
133+
} catch (error) {
134+
const t = await getTranslations({ locale, namespace: "common" })
135+
136+
// Return basic metadata for invalid paths
137+
return {
138+
title: t("page-not-found"),
139+
description: t("page-not-found-description"),
140+
}
141+
}
123142
}

app/[locale]/error.tsx

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,54 @@
11
"use client"
22

3-
import { useEffect } from "react"
3+
import { useEffect, useState } from "react"
44

5+
import RefreshCW from "@/components/icons/refresh-cw.svg"
56
import MainArticle from "@/components/MainArticle"
67
import Translation from "@/components/Translation"
8+
import { Button } from "@/components/ui/buttons/Button"
79
import { BaseLink } from "@/components/ui/Link"
810

11+
import { cn } from "@/lib/utils/cn"
12+
913
export default function Error({ error }: { error: Error; reset: () => void }) {
14+
const [refreshing, setRefreshing] = useState(false)
15+
const handleRefresh = () => {
16+
setRefreshing(true)
17+
window.location.reload()
18+
}
19+
20+
useEffect(() => {
21+
// Scroll view to top on error
22+
window.scrollTo(0, 0)
23+
}, [])
24+
1025
useEffect(() => {
1126
// TODO: log the error to an error reporting service
1227
console.error(error)
1328
}, [error])
1429

1530
return (
16-
<div className="mx-auto mb-0 mt-16 flex w-full flex-col items-center">
17-
<MainArticle className="my-8 w-full space-y-8 px-8 py-4">
18-
<h1>
19-
<Translation id="error-page-title" />
20-
</h1>
21-
<p className="mb-4">
22-
<Translation id="error-page-description" />
23-
</p>
24-
<p>
25-
<BaseLink href="/">
26-
<Translation id="error-page-home-link" />
27-
</BaseLink>
28-
</p>
29-
</MainArticle>
30-
</div>
31+
<MainArticle className="mb-32 mt-24 w-full space-y-8 px-8 py-4 md:mb-48 md:mt-32">
32+
<h1>
33+
<Translation id="error-page-title" />
34+
</h1>
35+
<p className="mb-4">
36+
<Translation id="error-page-description" />
37+
</p>
38+
39+
<Button onClick={handleRefresh}>
40+
<RefreshCW
41+
className={cn(
42+
"size-5",
43+
refreshing && "motion-safe:animate-spin motion-reduce:animate-pulse"
44+
)}
45+
/>
46+
<Translation id="refresh" />
47+
</Button>
48+
49+
<BaseLink href="/" className="block">
50+
<Translation id="error-page-home-link" />
51+
</BaseLink>
52+
</MainArticle>
3153
)
3254
}

app/[locale]/page.tsx

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { pick } from "lodash"
2+
import { notFound } from "next/navigation"
23
import {
34
getMessages,
45
getTranslations,
@@ -23,6 +24,8 @@ import {
2324
BLOG_FEEDS,
2425
BLOGS_WITHOUT_FEED,
2526
CALENDAR_DISPLAY_COUNT,
27+
DEFAULT_LOCALE,
28+
LOCALES_CODES,
2629
RSS_DISPLAY_COUNT,
2730
} from "@/lib/constants"
2831

@@ -61,6 +64,8 @@ const loadData = dataLoader(
6164
const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => {
6265
const { locale } = await params
6366

67+
if (!LOCALES_CODES.includes(locale)) return notFound()
68+
6469
setRequestLocale(locale)
6570

6671
const [
@@ -142,14 +147,26 @@ export async function generateMetadata({
142147
}) {
143148
const { locale } = await params
144149

145-
const t = await getTranslations({ locale, namespace: "page-index" })
150+
try {
151+
const t = await getTranslations({ locale, namespace: "page-index" })
152+
return await getMetadata({
153+
locale,
154+
slug: [""],
155+
title: t("page-index-meta-title"),
156+
description: t("page-index-meta-description"),
157+
})
158+
} catch (error) {
159+
const t = await getTranslations({
160+
locale: DEFAULT_LOCALE,
161+
namespace: "common",
162+
})
146163

147-
return await getMetadata({
148-
locale,
149-
slug: [""],
150-
title: t("page-index-meta-title"),
151-
description: t("page-index-meta-description"),
152-
})
164+
// Return basic metadata for invalid paths
165+
return {
166+
title: t("page-not-found"),
167+
description: t("page-not-found-description"),
168+
}
169+
}
153170
}
154171

155172
export default Page

app/not-found.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
"use client"
2-
3-
import Error from "next/error"
1+
import NotFoundPage from "@/components/NotFoundPage"
42

53
import { DEFAULT_LOCALE } from "@/lib/constants"
64

7-
export default function GlobalNotFound() {
5+
import LocaleLayout from "./[locale]/layout"
6+
7+
export default async function GlobalNotFound() {
88
return (
9-
<html lang={DEFAULT_LOCALE}>
10-
<body>
11-
<Error statusCode={404} />
12-
</body>
13-
</html>
9+
<LocaleLayout params={{ locale: DEFAULT_LOCALE }}>
10+
<NotFoundPage />
11+
</LocaleLayout>
1412
)
1513
}

src/components/NotFoundPage/index.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
1+
"use client"
2+
13
import MainArticle from "../MainArticle"
2-
import Translation from "../Translation"
34
import InlineLink from "../ui/Link"
45

6+
import useTranslation from "@/hooks/useTranslation"
7+
58
function NotFoundPage() {
9+
const { t } = useTranslation("common")
10+
611
return (
7-
<div className="mx-auto mb-0 mt-16 flex w-full flex-col items-center">
8-
<MainArticle className="my-8 w-full space-y-8 px-8 py-4">
9-
<h1>
10-
<Translation id="we-couldnt-find-that-page" />
11-
</h1>
12-
<p>
13-
<Translation id="try-using-search" />{" "}
14-
<InlineLink href="/">
15-
<Translation id="return-home" />
16-
</InlineLink>
17-
.
18-
</p>
19-
</MainArticle>
20-
</div>
12+
<MainArticle className="mb-32 mt-24 w-full space-y-8 px-8 py-4 md:mb-48 md:mt-32">
13+
<h1>{t("we-couldnt-find-that-page")}</h1>
14+
<p>
15+
{t("try-using-search")}{" "}
16+
<InlineLink href="/">{t("return-home")}</InlineLink>.
17+
</p>
18+
</MainArticle>
2119
)
2220
}
2321

src/components/icons/refresh-cw.svg

Lines changed: 4 additions & 0 deletions
Loading

src/intl/en/common.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@
8080
"enterprise": "Enterprise",
8181
"enterprise-mainnet": "Enterprise - Mainnet Ethereum",
8282
"enterprise-menu": "Enterprise Menu",
83+
"error-page-title": "Oops! Something went wrong",
84+
"error-page-description": "You can help us improve by reporting this issue on our <a href='https://github.com/ethereum/ethereum-org-website/issues/new?label%3A%22bug%20%F0%9F%90%9B%22&template=bug_report.yaml'>GitHub repository</a>.",
85+
"error-page-home-link": "Return to the home page",
8386
"esp": "Ecosystem Support Program",
8487
"eth-current-price": "Current ETH price (USD)",
8588
"ethereum": "Ethereum",
@@ -371,6 +374,8 @@
371374
"page-languages-want-more-paragraph": "ethereum.org translators are always translating pages in as many languages as possible. To see what they're working on right now or to sign up to join them, read about our",
372375
"page-languages-words": "words",
373376
"page-last-updated": "Page last updated",
377+
"page-not-found": "Page not found",
378+
"page-not-found-description": "The requested page could not be found.",
374379
"participate": "Participate",
375380
"participate-menu": "Participate menu",
376381
"payments-page": "Payments",
@@ -382,7 +387,7 @@
382387
"product-disclaimer": "Products and services are listed as a convenience for the Ethereum community. Inclusion of a product or service <strong>does not represent an endorsement</strong> from the ethereum.org website team, or the Ethereum Foundation.",
383388
"quizzes": "Quizzes",
384389
"quizzes-title": "Quiz Hub",
385-
"refresh": "Please refresh the page.",
390+
"refresh": "Refresh page",
386391
"regenerative-finance": "ReFi - Regenerative finance",
387392
"research": "Research",
388393
"research-menu": "Research menu",
@@ -444,9 +449,6 @@
444449
"verkle-trees": "Verkle trees",
445450
"wallets": "Wallets",
446451
"we-couldnt-find-that-page": "We couldn't find that page",
447-
"error-page-title": "Oops! Something went wrong",
448-
"error-page-description": "You can help us improve by reporting this issue on our <a href='https://github.com/ethereum/ethereum-org-website/issues/new?label%3A%22bug%20%F0%9F%90%9B%22&template=bug_report.yaml'>GitHub repository</a>.",
449-
"error-page-home-link": "Return to the home page",
450452
"web3": "What is Web3?",
451453
"web3-title": "Web3",
452454
"website-last-updated": "Website last updated",

src/lib/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,3 +1028,11 @@ export interface ITutorial {
10281028
lang: string
10291029
isExternal: boolean
10301030
}
1031+
1032+
export type PageParams = {
1033+
locale: string
1034+
}
1035+
1036+
export type SlugPageParams = PageParams & {
1037+
slug: string[]
1038+
}

0 commit comments

Comments
 (0)