diff --git a/.gitignore b/.gitignore index 2c2b5b1273b34..313580c9bb716 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,4 @@ cache # TypeScript tsconfig.tsbuildinfo -# Sentry Config File -.sentryclirc - dist/ diff --git a/.vscode/settings.json b/.vscode/settings.json index a7e0023eb6be3..f45e0fc02c2f8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "javascript.updateImportsOnFileMove.enabled": "always", - "typescript.updateImportsOnFileMove.enabled": "always" + "typescript.updateImportsOnFileMove.enabled": "always", + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/COLLABORATOR_GUIDE.md b/COLLABORATOR_GUIDE.md index ac5160ed08558..c4842f7e3e029 100644 --- a/COLLABORATOR_GUIDE.md +++ b/COLLABORATOR_GUIDE.md @@ -90,7 +90,6 @@ The Website also uses several other Open Source libraries (not limited to) liste - We use [Rehype](https://github.com/rehypejs/rehype) and [Remark](https://github.com/remarkjs/remark) to extend MDX functionality - We use [Storybook](https://storybook.js.org/) for Manual Testing and Visual Regression Tests of our React Components - Storybook also provides a sandboxed environment, which is very useful whilst for crafting React Components -- We use [Sentry](https://sentry.io/about) for reporting Exceptions and monitoring the performance and reliability of the application ## Code Editing diff --git a/apps/site/.storybook/main.ts b/apps/site/.storybook/main.ts index 174f47be55331..33db0069c6ee6 100644 --- a/apps/site/.storybook/main.ts +++ b/apps/site/.storybook/main.ts @@ -1,12 +1,8 @@ -import type { StorybookConfig } from '@storybook/nextjs'; -import classNames from 'classnames'; +import { join } from 'node:path'; -const rootClasses = classNames( - // note: this is hard-coded sadly as next/font can only be loaded within next.js context - '__variable_open-sans-normal', - // note: this is hard-coded sadly as next/font can only be loaded within next.js context - '__variable_ibm-plex-mono-normal' -); +import type { StorybookConfig } from '@storybook/react-webpack5'; + +const mocksFolder = join(__dirname, '../components/__mocks__'); const config: StorybookConfig = { stories: ['../components/**/*.stories.tsx'], @@ -14,22 +10,29 @@ const config: StorybookConfig = { staticDirs: ['../public'], typescript: { reactDocgen: false, check: false }, core: { disableTelemetry: true, disableWhatsNewNotifications: true }, - framework: { - name: '@storybook/nextjs', - options: { builder: { useSWC: true } }, - }, - previewBody: - // This `' + - // This adds the base styling for dark/light themes within Storybook. This is a Storybook-only style - `
`, + framework: '@storybook/react-webpack5', + swc: () => ({ jsc: { transform: { react: { runtime: 'automatic' } } } }), addons: [ + '@storybook/addon-webpack5-compiler-swc', '@storybook/addon-controls', '@storybook/addon-interactions', '@storybook/addon-themes', '@storybook/addon-viewport', + { + name: '@storybook/addon-styling-webpack', + options: { + rules: [ + { + test: /\.css$/, + use: [ + 'style-loader', + { loader: 'css-loader', options: { url: false } }, + 'postcss-loader', + ], + }, + ], + }, + }, ], webpack: async config => ({ ...config, @@ -39,7 +42,15 @@ const config: StorybookConfig = { performance: { hints: false }, // `nodevu` is a Node.js-specific package that requires Node.js modules // this is incompatible with Storybook. So we just mock the module - resolve: { ...config.resolve, alias: { '@nodevu/core': false } }, + resolve: { + ...config.resolve, + alias: { + '@nodevu/core': false, + 'next-intl/navigation': join(mocksFolder, './next-intl.mjs'), + '@/client-context': join(mocksFolder, './client-context.mjs'), + '@': join(__dirname, '../'), + }, + }, // We need to configure `node:` APIs as Externals to WebPack // since essentially they're not supported on the browser externals: { diff --git a/apps/site/.storybook/preview-head.html b/apps/site/.storybook/preview-head.html new file mode 100644 index 0000000000000..f33e88387634f --- /dev/null +++ b/apps/site/.storybook/preview-head.html @@ -0,0 +1,20 @@ + + + + + + diff --git a/apps/site/.storybook/preview.tsx b/apps/site/.storybook/preview.tsx index 43e68629f1c56..1cc9e387dce32 100644 --- a/apps/site/.storybook/preview.tsx +++ b/apps/site/.storybook/preview.tsx @@ -6,12 +6,10 @@ import { NextIntlClientProvider } from 'next-intl'; import { STORYBOOK_MODES, STORYBOOK_SIZES } from '@/.storybook/constants'; import { NotificationProvider } from '@/providers/notificationProvider'; -import '../next.fonts'; import '../styles/index.css'; const preview: Preview = { parameters: { - nextjs: { router: { basePath: '' }, appDirectory: true }, chromatic: { modes: STORYBOOK_MODES }, viewport: { defaultViewport: 'large', viewports: STORYBOOK_SIZES }, }, diff --git a/apps/site/app/[locale]/[...path]/page.tsx b/apps/site/app/[locale]/[...path]/page.tsx new file mode 100644 index 0000000000000..89bb335d8f427 --- /dev/null +++ b/apps/site/app/[locale]/[...path]/page.tsx @@ -0,0 +1,50 @@ +/** + * This file extends on the `page.tsx` file, which is the default file that is used to render + * the entry points for each locale and then also reused within the [...path] route to render the + * and contains all logic for rendering our dynamic and static routes within the Node.js Website. + * + * Note: that each `page.tsx` should have its own `generateStaticParams` to prevent clash of + * dynamic params, which will lead on static export errors and other sort of issues. + */ + +import * as basePage from '@/app/[locale]/page'; +import { ENABLE_STATIC_EXPORT } from '@/next.constants.mjs'; +import { dynamicRouter } from '@/next.dynamic.mjs'; +import { availableLocaleCodes } from '@/next.locales.mjs'; + +// This is the default Viewport Metadata +// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function +export const generateViewport = basePage.generateViewport; + +// This generates each page's HTML Metadata +// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata +export const generateMetadata = basePage.generateMetadata; + +// This provides all the possible paths that can be generated statically +// + provides all the paths that we support on the Node.js Website +export const generateStaticParams = async () => { + const allAvailableRoutes = await Promise.all( + // Gets all mapped routes to the Next.js Routing Engine by Locale + availableLocaleCodes.map(async (locale: string) => { + const routesForLanguage = await dynamicRouter.getRoutesByLanguage(locale); + + return routesForLanguage.map(pathname => + dynamicRouter.mapPathToRoute(locale, pathname) + ); + }) + ); + + return ENABLE_STATIC_EXPORT ? allAvailableRoutes.flat().sort() : []; +}; + +// Enforces that this route is used as static rendering +// Except whenever on the Development mode as we want instant-refresh when making changes +// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic +export const dynamic = 'force-static'; + +// Ensures that this endpoint is invalidated and re-executed every X minutes +// so that when new deployments happen, the data is refreshed +// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate +export const revalidate = 300; + +export default basePage.default; diff --git a/apps/site/app/[locale]/error.tsx b/apps/site/app/[locale]/error.tsx index f7c05407a2508..2a4d2e3431e71 100644 --- a/apps/site/app/[locale]/error.tsx +++ b/apps/site/app/[locale]/error.tsx @@ -1,15 +1,13 @@ 'use client'; import { ArrowRightIcon } from '@heroicons/react/24/solid'; -import { captureException } from '@sentry/nextjs'; import { useTranslations } from 'next-intl'; import type { FC } from 'react'; import Button from '@/components/Common/Button'; import GlowingBackdropLayout from '@/layouts/GlowingBackdrop'; -const ErrorPage: FC<{ error: Error }> = ({ error }) => { - captureException(error); +const ErrorPage: FC<{ error: Error }> = () => { const t = useTranslations(); return ( diff --git a/apps/site/app/[locale]/feed/[feed]/route.ts b/apps/site/app/[locale]/feed/[feed]/route.ts index a77e9d9ccd3b4..96d371d4f21a8 100644 --- a/apps/site/app/[locale]/feed/[feed]/route.ts +++ b/apps/site/app/[locale]/feed/[feed]/route.ts @@ -1,19 +1,20 @@ import { NextResponse } from 'next/server'; import provideWebsiteFeeds from '@/next-data/providers/websiteFeeds'; -import { VERCEL_REVALIDATE } from '@/next.constants.mjs'; import { siteConfig } from '@/next.json.mjs'; import { defaultLocale } from '@/next.locales.mjs'; // We only support fetching these pages from the /en/ locale code const locale = defaultLocale.code; -type StaticParams = { params: { feed: string; locale: string } }; +type StaticParams = { params: Promise<{ feed: string; locale: string }> }; // This is the Route Handler for the `GET` method which handles the request // for the Node.js Website Blog Feeds (RSS) // @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers -export const GET = async (_: Request, { params }: StaticParams) => { +export const GET = async (_: Request, props: StaticParams) => { + const params = await props.params; + // Generate the Feed for the given feed type (blog, releases, etc) const websiteFeed = provideWebsiteFeeds(params.feed); @@ -29,11 +30,9 @@ export const GET = async (_: Request, { params }: StaticParams) => { export const generateStaticParams = async () => siteConfig.rssFeeds.map(feed => ({ feed: feed.file, locale })); -// In this case we want to catch-all possible requests. This is so that if a non defined feed is -// requested we can manually return a 404 response for it instead of having Next.js handle it -// and return our top level custom 404 html page instead +// Enforces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams -export const dynamicParams = true; +export const dynamicParams = false; // Enforces that this route is cached and static as much as possible // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic @@ -42,4 +41,4 @@ export const dynamic = 'force-static'; // Ensures that this endpoint is invalidated and re-executed every X minutes // so that when new deployments happen, the data is refreshed // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate -export const revalidate = VERCEL_REVALIDATE; +export const revalidate = 300; diff --git a/apps/site/app/[locale]/layout.tsx b/apps/site/app/[locale]/layout.tsx index 87d0f28dd1801..b1887eea5c80e 100644 --- a/apps/site/app/[locale]/layout.tsx +++ b/apps/site/app/[locale]/layout.tsx @@ -1,7 +1,6 @@ import { Analytics } from '@vercel/analytics/react'; import { SpeedInsights } from '@vercel/speed-insights/next'; import classNames from 'classnames'; -import { getLocale } from 'next-intl/server'; import type { FC, PropsWithChildren } from 'react'; import BaseLayout from '@/layouts/Base'; @@ -15,13 +14,20 @@ import '@/styles/index.css'; const fontClasses = classNames(IBM_PLEX_MONO.variable, OPEN_SANS.variable); -const RootLayout: FC- This page has thrown a non-recoverable error. -
- -+ This page has thrown a non-recoverable error. +
+ +