diff --git a/middleware.ts b/middleware.ts index 97d6926076d..7d9a4aaf929 100644 --- a/middleware.ts +++ b/middleware.ts @@ -7,6 +7,17 @@ import { DEFAULT_LOCALE } from "./src/lib/constants" const handleI18nRouting = createMiddleware(routing) export default function middleware(request: NextRequest) { + // Normalize to lowercase paths site-wide (URLs are case-insensitive by spec, + // but our routes are defined in lowercase). Do this BEFORE i18n routing. + const originalPath = request.nextUrl.pathname + const lowerPath = originalPath.toLowerCase() + if (originalPath !== lowerPath) { + const url = request.nextUrl.clone() + url.pathname = lowerPath + return NextResponse.redirect(url, 301) + } + + // Handle i18n routing const response = handleI18nRouting(request) // Upgrade default-locale strip redirects from 307 to 301 for SEO diff --git a/next.config.js b/next.config.js index 07b4140bf75..7a70e377634 100644 --- a/next.config.js +++ b/next.config.js @@ -9,6 +9,10 @@ const createNextIntlPlugin = require("next-intl/plugin") const { withSentryConfig } = require("@sentry/nextjs") +const redirects = require("./redirects.config") + +const i18nConfigJson = require("./i18n.config.json") + const withNextIntl = createNextIntlPlugin() const LIMIT_CPUS = Number(process.env.LIMIT_CPUS ?? 2) @@ -135,6 +139,39 @@ module.exports = (phase, { defaultConfig }) => { }, ] }, + async redirects() { + // Build a strict locale matcher from configured locales + const LOCALE_ALTS = i18nConfigJson.map(({ code }) => code).join("|") // e.g. "en|es|fr|..." + + // Helper function to generate both English (no prefix) and locale-prefixed redirects + const createRedirect = (source, destination, permanent = true) => { + // For external URLs, don't modify the destination + const isExternal = destination.startsWith("http") + + // English / default-locale: no prefix in source or destination + const defaultRedirect = { source, destination, permanent } + + // Locale-prefixed: only match allowed locales (prevents matching arbitrary segments) + const localeRedirect = { + source: `/:locale(${LOCALE_ALTS})${source}`, + destination: isExternal ? destination : `/:locale${destination}`, + permanent, + } + + return [defaultRedirect, localeRedirect] + } + + return [ + // Custom locale aliases redirects + { source: "/no/:path*", destination: "/nb/:path*", permanent: true }, + { source: "/ph/:path*", destination: "/fil/:path*", permanent: true }, + + // All primary redirects + ...redirects.flatMap(([source, destination, permanent]) => + createRedirect(source, destination, permanent) + ), + ] + }, } nextConfig = { diff --git a/public/_redirects b/public/_redirects deleted file mode 100644 index c9770edeefa..00000000000 --- a/public/_redirects +++ /dev/null @@ -1,190 +0,0 @@ -/discord https://discord.gg/ethereum-org 301! - -/*/discord https://discord.gg/ethereum-org 301! - -/pdfs/* / 301! - -/brand /assets/ 301! - -/ether /what-is-ether/ 301! - -/token /developers/ 301! - -/crowdsale /developers/ 301! - -/cli /developers/ 301! - -/greeter /developers/ 301! - -/search / 301! - -/use /apps/ 301! - -/dapps /apps/ 301! - -/beginners /what-is-ethereum/ 301! - -/eth2/ /roadmap/ 301! - -/build/ /developers/learning-tools/ 301! - -/nfts/ /nft/ 301! - -/payments/ /payments/ 301! - -/daos/ /dao/ 301! - -/layer2/ /layer-2/ 301! - -/*/layer2/ /:splat/layer-2/ 301! - -/grants/ /community/grants/ 301! - -/no/* /nb/:splat 301! - -/ph/* /fil/:splat 301! - -/java/ /developers/docs/programming-languages/java/ 301! - -/python/ /developers/docs/programming-languages/python/ 301! - -/javascript/ /developers/docs/programming-languages/javascript/ 301! - -/golang/ /developers/docs/programming-languages/golang/ 301! - -/rust/ /developers/docs/programming-languages/rust/ 301! - -/dot-net/ /developers/docs/programming-languages/dot-net/ 301! - -/delphi/ /developers/docs/programming-languages/delphi/ 301! - -/dart/ /developers/docs/programming-languages/dart/ 301! - -/developers/docs/mining/ /developers/docs/consensus-mechanisms/pow/mining/ 301! - -/*/beginners /:splat/what-is-ethereum/ 301! - -/*/build /:splat/developers/learning-tools/ 301! - -/*/eth2/beacon-chain/ /:splat/upgrades/beacon-chain/ 301! - -/*/eth2/the-beacon-chain/ /:splat/upgrades/beacon-chain/ 301! - -/*/upgrades/the-beacon-chain/ /:splat/upgrades/beacon-chain/ 301! - -/*/eth2/merge /:splat/upgrades/merge/ 301! - -/*/eth2/the-merge /:splat/upgrades/merge/ 301! - -/*/upgrades/the-merge /:splat/upgrades/merge/ 301! - -/*/eth2/docking /:splat/upgrades/merge/ 301! - -/*/upgrades/docking /:splat/upgrades/merge/ 301! - -/*/eth2/the-docking /:splat/upgrades/merge/ 301! - -/*/upgrades/the-docking /:splat/upgrades/merge/ 301! - -/*/eth2/shard-chains/ /:splat/roadmap/danksharding/ 301! - -/*/upgrades/shard-chains/ /:splat/roadmap/danksharding/ 301! - -/upgrades/sharding/ /roadmap/danksharding/ 301! - -/*/upgrades/sharding/ /:splat/roadmap/danksharding/ 301! - -/upgrades/shard-chains/ /roadmap/danksharding/ 301! - -/upgrades/merge /roadmap/merge/ 301! - -/*/upgrades/merge /:splat/roadmap/merge/ 301! - -/upgrades/merge/issuance /roadmap/merge/issuance 301! - -/*/upgrades/merge/issuance /:splat/roadmap/merge/issuance 301! - -/upgrades/beacon-chain /roadmap/beacon-chain 301! - -/*/upgrades/beacon-chain /:splat/roadmap/beacon-chain 301! - -/upgrades/vision/ /roadmap/ 301! - -/*/upgrades/vision/ /:splat/roadmap/ 301! - -/upgrades /roadmap 301! - -/*/upgrades /:splat/roadmap 301! - -/upgrades/get-involved /contributing 301! - -/*/upgrades/get-involved /:splat/contributing 301! - -/*/eth2/staking/ /:splat/staking/ 301! - -/*/eth2/vision/ /:splat/roadmap/vision/ 301! - -/*/eth2/get-involved/ /:splat/upgrades/get-involved/ 301! - -/*/eth2/get-involved/bug-bounty/ /:splat/bug-bounty/ 301! - -/*/upgrades/get-involved/bug-bounty/ /:splat/bug-bounty/ 301! - -/*/eth2/deposit-contract/ /:splat/staking/deposit-contract/ 301! - -/*/eth2 /:splat/upgrades/ 301! - -/*/developers/docs/scaling/layer-2-rollups /:splat/developers/docs/scaling 301! - -/*/about/web-developer /:splat/about/#open-jobs 301! - -/*/about/product-designer /:splat/about/#open-jobs 301! - -/*/use /:splat/apps/ 301! - -# Exception: don't redirect developers/docs/dapps paths -/*/developers/docs/dapps* /:splat/developers/docs/dapps:splat 200! - -/*/dapps /:splat/apps/ 301! - -/*/contributing/translation-program/translation-guide/ /:splat/contributing/translation-program/faq/ 301! - -/*/contributing/translation-program/content-versions/ /:splat/contributing/translation-program/ 301! - -/*/contributing/translation-program/content-buckets/ /:splat/contributing/translation-program/ 301! - -/*/developers/docs/smart-contracts/source-code-verification/ /:splat/developers/docs/smart-contracts/verifying/ 301! - -/*/developers/docs/smart-contracts/upgrading-smart-contracts/ /:splat/developers/docs/smart-contracts/upgrading/ 301! - -/staking/withdraws /staking/withdrawals/ 301! - -/*/writing-cohort https://ethereumwriterscohort.carrd.co/ 301! - -/*/staking/withdraws /:splat/staking/withdrawals/ 301! - -/*/guides/how-to-register-an-ethereum-account /:splat/guides/how-to-create-an-ethereum-account/ 301! - -/*/deprecated-software /:splat/apps/ 301! - -/*/enterprise/private-ethereum/ /:splat/enterprise/ 301! - -/dashboards /resources 301! - -/*/dashboards /:splat/resources 301! - -/tds /trillion-dollar-security 301! - -/*/tds /:splat/trillion-dollar-security 301! - -/10-years /10years 301! - -/*/10-years /:splat/10years 301! - -/history /ethereum-forks 301! - -/*/history /:splat/ethereum-forks 301! - -/eth /what-is-ether 301! - -/*/eth /:splat/what-is-ether 301! diff --git a/public/content/ethereum-forks/index.md b/public/content/ethereum-forks/index.md index 48073f596e5..3dbac7db7dd 100644 --- a/public/content/ethereum-forks/index.md +++ b/public/content/ethereum-forks/index.md @@ -73,7 +73,7 @@ Looking for future protocol upgrades? [Learn about upcoming upgrades on the Ethe ## 2025 {#2025} -### Fulu-Osaka ("Fusaka", _in progress_) {#fusaka} +### Fulu-Osaka ("Fusaka") {#fusaka} diff --git a/redirects.config.js b/redirects.config.js new file mode 100644 index 00000000000..9b971f002df --- /dev/null +++ b/redirects.config.js @@ -0,0 +1,104 @@ +// All primary redirects ([source, destination, permanent? (default true)]) + +/** @type { [string, string, boolean | undefined][] } */ +module.exports = [ + ["/discord", "https://discord.gg/ethereum-org"], + ["/writing-cohort", "https://ethereumwriterscohort.carrd.co/"], + ["/pdfs/:path*", "/"], + ["/brand", "/assets/"], + ["/ethereum.html", "/what-is-ethereum/"], + ["/ether", "/what-is-ether/"], + ["/eth", "/what-is-ether/"], + ["/token", "/developers/"], + ["/crowdsale", "/developers/"], + ["/cli", "/developers/"], + ["/greeter", "/developers/"], + ["/roadmap/vision", "/roadmap/"], + ["/search", "/"], + ["/garden", "/roadmap/"], + ["/download", "/wallets/find-wallet/"], + ["/how", "/guides/"], + ["/content/:path*", "/:path*"], + ["/nfts", "/nft/"], + ["/daos", "/dao/"], + ["/layer2", "/layer-2/"], + ["/grants", "/community/grants/"], + ["/java", "/developers/docs/programming-languages/java/"], + ["/python", "/developers/docs/programming-languages/python/"], + ["/javascript", "/developers/docs/programming-languages/javascript/"], + ["/golang", "/developers/docs/programming-languages/golang/"], + ["/rust", "/developers/docs/programming-languages/rust/"], + ["/dot-net", "/developers/docs/programming-languages/dot-net/"], + ["/delphi", "/developers/docs/programming-languages/delphi/"], + ["/dart", "/developers/docs/programming-languages/dart/"], + ["/languages", "/community/language-resources/"], + [ + "/developers/docs/mining", + "/developers/docs/consensus-mechanisms/pow/mining/", + ], + ["/beginners", "/what-is-ethereum/"], + ["/build", "/developers/learning-tools/"], + ["/eth2/beacon-chain", "/roadmap/beacon-chain/"], + ["/eth2/the-beacon-chain", "/roadmap/beacon-chain/"], + ["/upgrades/the-beacon-chain", "/roadmap/beacon-chain/"], + ["/eth2/merge", "/roadmap/merge/"], + ["/eth2/the-merge", "/roadmap/merge/"], + ["/upgrades/the-merge", "/roadmap/merge/"], + ["/eth2/docking", "/roadmap/merge/"], + ["/upgrades/docking", "/roadmap/merge/"], + ["/eth2/the-docking", "/roadmap/merge/"], + ["/upgrades/the-docking", "/roadmap/merge/"], + ["/eth2/shard-chains", "/roadmap/danksharding/"], + ["/upgrades/shard-chains", "/roadmap/danksharding/"], + ["/upgrades/sharding", "/roadmap/danksharding/"], + ["/upgrades/merge", "/roadmap/merge/"], + ["/upgrades/merge/issuance", "/roadmap/merge/issuance"], + ["/upgrades/beacon-chain", "/roadmap/beacon-chain"], + ["/upgrades/vision", "/roadmap/"], + ["/upgrades", "/roadmap"], + ["/upgrades/get-involved", "/contributing"], + ["/eth2/staking", "/staking/"], + ["/eth2/vision", "/roadmap/vision/"], + ["/eth2/get-involved", "/contributing/"], + ["/eth2/get-involved/bug-bounty", "/bug-bounty/"], + ["/upgrades/get-involved/bug-bounty", "/bug-bounty/"], + ["/eth2/deposit-contract", "/staking/deposit-contract/"], + ["/eth2", "/roadmap/"], + ["/developers/docs/scaling/layer-2-rollups", "/developers/docs/scaling"], + ["/developers/docs/layer-2-scaling", "/layer-2/"], + ["/about/web-developer", "/about/#open-jobs"], + ["/about/product-designer", "/about/#open-jobs"], + ["/use", "/apps/"], + ["/dapps", "/apps/"], + [ + "/contributing/translation-program/translation-guide", + "/contributing/translation-program/faq/", + ], + [ + "/contributing/translation-program/content-versions", + "/contributing/translation-program/", + ], + [ + "/contributing/translation-program/content-buckets", + "/contributing/translation-program/", + ], + [ + "/developers/docs/smart-contracts/source-code-verification", + "/developers/docs/smart-contracts/verifying/", + ], + [ + "/developers/docs/smart-contracts/upgrading-smart-contracts", + "/developers/docs/smart-contracts/upgrading/", + ], + ["/staking/withdraws", "/staking/withdrawals/"], + [ + "/guides/how-to-register-an-ethereum-account", + "/guides/how-to-create-an-ethereum-account/", + ], + ["/deprecated-software", "/apps/"], + ["/enterprise/private-ethereum", "/enterprise/"], + ["/dashboards", "/resources"], + ["/tds", "/trillion-dollar-security"], + ["/10-years", "/10years"], + ["/history", "/ethereum-forks"], +] diff --git a/src/components/Banners/FusakaBanner/FusakaCountdown.tsx b/src/components/Banners/FusakaBanner/FusakaCountdown.tsx index 2fbc1a47546..48d1de09042 100644 --- a/src/components/Banners/FusakaBanner/FusakaCountdown.tsx +++ b/src/components/Banners/FusakaBanner/FusakaCountdown.tsx @@ -128,14 +128,18 @@ const FusakaCountdown = ({ liveNowText }: { liveNowText: string }) => { }, []) if (timeUnits.isExpired) { - return

{liveNowText}

+ return ( +

+ {liveNowText} +

+ ) } return ( -
+
{timeUnits.days > 0 && (
-

+

{String(timeUnits.days).padStart(2, "0")}

@@ -144,13 +148,13 @@ const FusakaCountdown = ({ liveNowText }: { liveNowText: string }) => {

)}
-

+

{String(timeUnits.hours).padStart(2, "0")}

{labels.hours}

-

+

{String(timeUnits.minutes).padStart(2, "0")}

@@ -159,7 +163,7 @@ const FusakaCountdown = ({ liveNowText }: { liveNowText: string }) => {

{timeUnits.seconds !== null && (
-

+

{String(timeUnits.seconds).padStart(2, "0")}

diff --git a/src/components/Banners/FusakaBanner/index.tsx b/src/components/Banners/FusakaBanner/index.tsx index 0171b498392..17d1b1f0cad 100644 --- a/src/components/Banners/FusakaBanner/index.tsx +++ b/src/components/Banners/FusakaBanner/index.tsx @@ -9,28 +9,27 @@ const FusakaBanner = async () => { const t = await getTranslations({ locale, namespace: "page-index" }) return ( - -

-
-

+ +

+
+

FUSAKA

-

+

{t("page-index-fusaka-network-upgrade")}

-

+

{t("page-index-fusaka-description")}{" "} {t("page-index-fusaka-read-more")} - .

-
-

+

+

{t.rich("page-index-fusaka-going-live-in", { br: () =>
, })} diff --git a/src/data/networkUpgradeSummaryData.ts b/src/data/networkUpgradeSummaryData.ts index 8b39b45d27c..d72cbb91de3 100644 --- a/src/data/networkUpgradeSummaryData.ts +++ b/src/data/networkUpgradeSummaryData.ts @@ -3,9 +3,12 @@ import type { NetworkUpgradeData } from "@/lib/types" const networkUpgradeSummaryData: NetworkUpgradeData = { fusaka: { dateTimeAsString: "2025-12-03T21:49:11.000Z", + ethPriceInUSD: 3149, + waybackLink: + "https://web.archive.org/web/20251203214416/https://ethereum.org/", + blockNumber: 23935694, epochNumber: 411392, slotNumber: 411392 * 32, - isPending: true, }, pectra: { dateTimeAsString: "2025-05-07T10:05:11.000Z",