The Node.js® Website

feat: introduced not found / error layout (#6247)

authored by Caner Akdas and committed by GitHub bd543e00 4664438b

+30 -2
app/[locale]/error.tsx
··· 1 'use client'; 2 3 import { captureException } from '@sentry/nextjs'; 4 import type { FC } from 'react'; 5 import { useMemo } from 'react'; 6 7 - const ErrorPage: FC<{ error: Error }> = ({ error }) => { 8 useMemo(() => captureException(error), [error]); 9 10 return ( ··· 15 ); 16 }; 17 18 - export default ErrorPage;
··· 1 'use client'; 2 3 import { captureException } from '@sentry/nextjs'; 4 + import { useTranslations } from 'next-intl'; 5 import type { FC } from 'react'; 6 import { useMemo } from 'react'; 7 8 + import Button from '@/components/Common/Button'; 9 + import CenteredLayout from '@/layouts/New/Centered'; 10 + import { ENABLE_WEBSITE_REDESIGN } from '@/next.constants.mjs'; 11 + 12 + /** @deprecated remove legacy component when website redesign is done */ 13 + const LegacyErrorPage: FC<{ error: Error }> = ({ error }) => { 14 useMemo(() => captureException(error), [error]); 15 16 return ( ··· 21 ); 22 }; 23 24 + const ErrorPage: FC<{ error: Error }> = ({ error }) => { 25 + captureException(error); 26 + const t = useTranslations(); 27 + 28 + return ( 29 + <CenteredLayout> 30 + <div className="glowingBackdrop" /> 31 + 32 + <main> 33 + 500 34 + <h1 className="special -mt-4"> 35 + {t('layouts.error.internalServerError.title')} 36 + </h1> 37 + <p className="-mt-4 max-w-sm text-center text-lg"> 38 + {t('layouts.error.internalServerError.description')} 39 + </p> 40 + <Button href="/">{t('layouts.error.backToHome')}</Button> 41 + </main> 42 + </CenteredLayout> 43 + ); 44 + }; 45 + 46 + export default ENABLE_WEBSITE_REDESIGN ? ErrorPage : LegacyErrorPage;
+26 -2
app/[locale]/not-found.tsx
··· 3 import { useTranslations } from 'next-intl'; 4 import type { FC } from 'react'; 5 6 - const LocalizedNotFound: FC = () => { 7 const t = useTranslations(); 8 9 return ( ··· 14 ); 15 }; 16 17 - export default LocalizedNotFound;
··· 3 import { useTranslations } from 'next-intl'; 4 import type { FC } from 'react'; 5 6 + import Button from '@/components/Common/Button'; 7 + import CenteredLayout from '@/layouts/New/Centered'; 8 + import { ENABLE_WEBSITE_REDESIGN } from '@/next.constants.mjs'; 9 + 10 + /** @deprecated remove legacy component when website redesign is done */ 11 + const LegacyNotFoundPage: FC = () => { 12 const t = useTranslations(); 13 14 return ( ··· 19 ); 20 }; 21 22 + const NotFoundPage: FC = () => { 23 + const t = useTranslations(); 24 + 25 + return ( 26 + <CenteredLayout> 27 + <div className="glowingBackdrop" /> 28 + 29 + <main> 30 + 404 31 + <h1 className="special -mt-4">{t('layouts.error.notFound.title')}</h1> 32 + <p className="-mt-4 max-w-sm text-center text-lg"> 33 + {t('layouts.error.notFound.description')} 34 + </p> 35 + <Button href="/">{t('layouts.error.backToHome')}</Button> 36 + </main> 37 + </CenteredLayout> 38 + ); 39 + }; 40 + 41 + export default ENABLE_WEBSITE_REDESIGN ? NotFoundPage : LegacyNotFoundPage;
+35 -2
app/global-error.tsx
··· 5 import type { FC } from 'react'; 6 import { useMemo } from 'react'; 7 8 - const GlobalErrorPage: FC<{ error: Error }> = ({ error }) => { 9 useMemo(() => captureException(error), [error]); 10 11 return ( ··· 17 ); 18 }; 19 20 - export default GlobalErrorPage;
··· 5 import type { FC } from 'react'; 6 import { useMemo } from 'react'; 7 8 + import Button from '@/components/Common/Button'; 9 + import BaseLayout from '@/layouts/BaseLayout'; 10 + import CenteredLayout from '@/layouts/New/Centered'; 11 + import { ENABLE_WEBSITE_REDESIGN } from '@/next.constants.mjs'; 12 + 13 + /** @deprecated remove legacy component when website redesign is done */ 14 + const LegacyGlobalErrorPage: FC<{ error: Error }> = ({ error }) => { 15 useMemo(() => captureException(error), [error]); 16 17 return ( ··· 23 ); 24 }; 25 26 + const GlobalErrorPage: FC<{ error: Error }> = ({ error }) => { 27 + captureException(error); 28 + 29 + return ( 30 + <html> 31 + <body> 32 + <BaseLayout> 33 + <CenteredLayout> 34 + <div className="glowingBackdrop" /> 35 + 36 + <main> 37 + 500 38 + <h1 className="special -mt-4">Internal Server Error</h1> 39 + <p className="-mt-4 max-w-sm text-center text-lg"> 40 + This page has thrown a non-recoverable error. 41 + </p> 42 + <Button href="/">Back to Home</Button> 43 + </main> 44 + </CenteredLayout> 45 + </BaseLayout> 46 + </body> 47 + </html> 48 + ); 49 + }; 50 + 51 + export default ENABLE_WEBSITE_REDESIGN 52 + ? GlobalErrorPage 53 + : LegacyGlobalErrorPage;
+11
i18n/locales/en.json
··· 199 "weekly-updates": "Weekly Updates", 200 "wg": "Working Groups" 201 } 202 } 203 }, 204 "pages": {
··· 199 "weekly-updates": "Weekly Updates", 200 "wg": "Working Groups" 201 } 202 + }, 203 + "error": { 204 + "notFound": { 205 + "title": "Page could not be found", 206 + "description": "Sorry, we couldn’t find the page you’re after! Try starting again from the homepage." 207 + }, 208 + "internalServerError": { 209 + "title": "Internal Server Error", 210 + "description": "This page has thrown a non-recoverable error." 211 + }, 212 + "backToHome": "Back to Home" 213 } 214 }, 215 "pages": {
+18
layouts/New/Centered.tsx
···
··· 1 + import type { FC, PropsWithChildren } from 'react'; 2 + 3 + import WithFooter from '@/components/withFooter'; 4 + import WithNavBar from '@/components/withNavBar'; 5 + 6 + import styles from './layouts.module.css'; 7 + 8 + const CenteredLayout: FC<PropsWithChildren> = ({ children }) => ( 9 + <> 10 + <WithNavBar /> 11 + 12 + <div className={styles.centeredLayout}>{children}</div> 13 + 14 + <WithFooter /> 15 + </> 16 + ); 17 + 18 + export default CenteredLayout;
+5 -12
layouts/New/Home.tsx
··· 1 import type { FC, PropsWithChildren } from 'react'; 2 3 - import WithFooter from '@/components/withFooter'; 4 - import WithNavBar from '@/components/withNavBar'; 5 6 import styles from './layouts.module.css'; 7 8 const HomeLayout: FC<PropsWithChildren> = ({ children }) => ( 9 - <> 10 - <WithNavBar /> 11 12 - <div className={styles.homeLayout}> 13 - <div className="glowingBackdrop" /> 14 - 15 - <main>{children}</main> 16 - </div> 17 - 18 - <WithFooter /> 19 - </> 20 ); 21 22 export default HomeLayout;
··· 1 import type { FC, PropsWithChildren } from 'react'; 2 3 + import CenteredLayout from '@/layouts/New/Centered'; 4 5 import styles from './layouts.module.css'; 6 7 const HomeLayout: FC<PropsWithChildren> = ({ children }) => ( 8 + <CenteredLayout> 9 + <div className="glowingBackdrop" /> 10 11 + <main className={styles.homeLayout}>{children}</main> 12 + </CenteredLayout> 13 ); 14 15 export default HomeLayout;
+59 -56
layouts/New/layouts.module.css
··· 58 } 59 } 60 61 - .homeLayout { 62 @apply flex 63 w-full 64 items-center ··· 70 71 main { 72 @apply items-center 73 - justify-center 74 - gap-8 75 - md:flex-row 76 - md:gap-14 77 - xl:gap-28 78 - 2xl:gap-32; 79 80 - section { 81 - &:nth-of-type(1) { 82 @apply flex 83 - max-w-[500px] 84 - flex-[1_0] 85 flex-col 86 - gap-8; 87 88 - > div { 89 - @apply flex 90 - max-w-[400px] 91 - flex-col 92 - gap-4; 93 94 - p { 95 - @apply text-base 96 - md:text-lg; 97 - } 98 - 99 - small { 100 - @apply text-center 101 - text-sm 102 - text-neutral-800 103 - xs:text-xs 104 - dark:text-neutral-400; 105 - } 106 } 107 } 108 109 - &:nth-of-type(2) { 110 - @apply flex 111 - max-w-md 112 - flex-[1_1] 113 - flex-col 114 - items-center 115 - gap-4 116 - md:max-w-2xl 117 - lg:max-w-3xl; 118 119 - > div { 120 - @apply w-fit; 121 122 - div[data-state='active'] a { 123 - @apply border-none 124 - bg-neutral-900 125 - px-3 126 - py-1.5 127 - text-sm 128 - font-medium; 129 130 - &:hover { 131 - @apply bg-neutral-800; 132 - } 133 } 134 } 135 136 - > p { 137 - @apply text-center 138 - text-sm 139 - text-neutral-800 140 - dark:text-neutral-200; 141 - } 142 } 143 } 144 }
··· 58 } 59 } 60 61 + .centeredLayout { 62 @apply flex 63 w-full 64 items-center ··· 70 71 main { 72 @apply items-center 73 + justify-center; 74 + } 75 + } 76 + 77 + .homeLayout { 78 + @apply gap-8 79 + md:flex-row 80 + md:gap-14 81 + xl:gap-28 82 + 2xl:gap-32; 83 + 84 + section { 85 + &:nth-of-type(1) { 86 + @apply flex 87 + max-w-[500px] 88 + flex-[1_0] 89 + flex-col 90 + gap-8; 91 92 + > div { 93 @apply flex 94 + max-w-[400px] 95 flex-col 96 + gap-4; 97 98 + p { 99 + @apply text-base 100 + md:text-lg; 101 + } 102 103 + small { 104 + @apply text-center 105 + text-sm 106 + text-neutral-800 107 + xs:text-xs 108 + dark:text-neutral-400; 109 } 110 } 111 + } 112 113 + &:nth-of-type(2) { 114 + @apply flex 115 + max-w-md 116 + flex-[1_1] 117 + flex-col 118 + items-center 119 + gap-4 120 + md:max-w-2xl 121 + lg:max-w-3xl; 122 123 + > div { 124 + @apply w-fit; 125 126 + div[data-state='active'] a { 127 + @apply border-none 128 + bg-neutral-900 129 + px-3 130 + py-1.5 131 + text-sm 132 + font-medium; 133 134 + &:hover { 135 + @apply bg-neutral-800; 136 } 137 } 138 + } 139 140 + > p { 141 + @apply text-center 142 + text-sm 143 + text-neutral-800 144 + dark:text-neutral-200; 145 } 146 } 147 }
-2
next.dynamic.constants.mjs
··· 15 * @type {((route: import('./types').RouteSegment) => boolean)[]} A list of Ignored Routes by Regular Expressions 16 */ 17 export const IGNORED_ROUTES = [ 18 - // This is used to ignore the 404 route for the static generation (/404) 19 - ({ pathname }) => pathname === '404', 20 // This is used to ignore all blog routes except for the English language 21 ({ locale, pathname }) => 22 locale !== defaultLocale.code && /^blog\//.test(pathname),
··· 15 * @type {((route: import('./types').RouteSegment) => boolean)[]} A list of Ignored Routes by Regular Expressions 16 */ 17 export const IGNORED_ROUTES = [ 18 // This is used to ignore all blog routes except for the English language 19 ({ locale, pathname }) => 20 locale !== defaultLocale.code && /^blog\//.test(pathname),
-9
pages/en/404.md
··· 1 - --- 2 - layout: page.hbs 3 - permalink: false 4 - title: 404 5 - --- 6 - 7 - ## 404: Page could not be found 8 - 9 - ### ENOENT: no such file or directory
···
+4
styles/new/markdown.css
··· 73 dark:text-green-300; 74 } 75 76 &:has(code) { 77 @apply xs:decoration-neutral-800 78 dark:xs:decoration-neutral-200;
··· 73 dark:text-green-300; 74 } 75 76 + &[role='button'] { 77 + @apply xs:no-underline; 78 + } 79 + 80 &:has(code) { 81 @apply xs:decoration-neutral-800 82 dark:xs:decoration-neutral-200;
+1
tailwind.config.ts
··· 8 './layouts/**/*.tsx', 9 './.storybook/preview.tsx', 10 './.storybook/main.ts', 11 ], 12 theme: { 13 colors: {
··· 8 './layouts/**/*.tsx', 9 './.storybook/preview.tsx', 10 './.storybook/main.ts', 11 + './app/**/*.tsx', 12 ], 13 theme: { 14 colors: {