Skip to content

Commit 7f751fd

Browse files
ovflowdCopilotavivkelleramannnbmuenzenmeyer
authored
chore: upgrade to next 15 (#7155)
Co-authored-by: Claudio Wunder <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: RedYetiDev <[email protected]> Co-authored-by: Jan Amann <[email protected]> Co-authored-by: Brian Muenzenmeyer <[email protected]>
1 parent fa6fbd9 commit 7f751fd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+5391
-11709
lines changed

.gitignore

-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,4 @@ cache
3131
# TypeScript
3232
tsconfig.tsbuildinfo
3333

34-
# Sentry Config File
35-
.sentryclirc
36-
3734
dist/

.vscode/settings.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"editor.formatOnSave": true,
44
"editor.defaultFormatter": "esbenp.prettier-vscode",
55
"javascript.updateImportsOnFileMove.enabled": "always",
6-
"typescript.updateImportsOnFileMove.enabled": "always"
6+
"typescript.updateImportsOnFileMove.enabled": "always",
7+
"typescript.tsdk": "node_modules/typescript/lib"
78
}

COLLABORATOR_GUIDE.md

-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ The Website also uses several other Open Source libraries (not limited to) liste
9090
- We use [Rehype](https://github.com/rehypejs/rehype) and [Remark](https://github.com/remarkjs/remark) to extend MDX functionality
9191
- We use [Storybook](https://storybook.js.org/) for Manual Testing and Visual Regression Tests of our React Components
9292
- Storybook also provides a sandboxed environment, which is very useful whilst for crafting React Components
93-
- We use [Sentry](https://sentry.io/about) for reporting Exceptions and monitoring the performance and reliability of the application
9493

9594
## Code Editing
9695

apps/site/.storybook/main.ts

+31-20
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
1-
import type { StorybookConfig } from '@storybook/nextjs';
2-
import classNames from 'classnames';
1+
import { join } from 'node:path';
32

4-
const rootClasses = classNames(
5-
// note: this is hard-coded sadly as next/font can only be loaded within next.js context
6-
'__variable_open-sans-normal',
7-
// note: this is hard-coded sadly as next/font can only be loaded within next.js context
8-
'__variable_ibm-plex-mono-normal'
9-
);
3+
import type { StorybookConfig } from '@storybook/react-webpack5';
4+
5+
const mocksFolder = join(__dirname, '../components/__mocks__');
106

117
const config: StorybookConfig = {
128
stories: ['../components/**/*.stories.tsx'],
139
logLevel: 'error',
1410
staticDirs: ['../public'],
1511
typescript: { reactDocgen: false, check: false },
1612
core: { disableTelemetry: true, disableWhatsNewNotifications: true },
17-
framework: {
18-
name: '@storybook/nextjs',
19-
options: { builder: { useSWC: true } },
20-
},
21-
previewBody:
22-
// This `<style>` is necessary to simulate what `next-themes` (ThemeProvider) does on real applications
23-
// `next-theme` automatically injects the color-scheme based on the system preference or the current applied theme
24-
// on Storybook we don't use `next-theme` as we want to simulate themes
25-
'<style>:root { color-scheme: light; } html[data-theme="dark"] { color-scheme: dark; }</style>' +
26-
// This adds the base styling for dark/light themes within Storybook. This is a Storybook-only style
27-
`<body class="${rootClasses}"></body>`,
13+
framework: '@storybook/react-webpack5',
14+
swc: () => ({ jsc: { transform: { react: { runtime: 'automatic' } } } }),
2815
addons: [
16+
'@storybook/addon-webpack5-compiler-swc',
2917
'@storybook/addon-controls',
3018
'@storybook/addon-interactions',
3119
'@storybook/addon-themes',
3220
'@storybook/addon-viewport',
21+
{
22+
name: '@storybook/addon-styling-webpack',
23+
options: {
24+
rules: [
25+
{
26+
test: /\.css$/,
27+
use: [
28+
'style-loader',
29+
{ loader: 'css-loader', options: { url: false } },
30+
'postcss-loader',
31+
],
32+
},
33+
],
34+
},
35+
},
3336
],
3437
webpack: async config => ({
3538
...config,
@@ -39,7 +42,15 @@ const config: StorybookConfig = {
3942
performance: { hints: false },
4043
// `nodevu` is a Node.js-specific package that requires Node.js modules
4144
// this is incompatible with Storybook. So we just mock the module
42-
resolve: { ...config.resolve, alias: { '@nodevu/core': false } },
45+
resolve: {
46+
...config.resolve,
47+
alias: {
48+
'@nodevu/core': false,
49+
'next-intl/navigation': join(mocksFolder, './next-intl.mjs'),
50+
'@/client-context': join(mocksFolder, './client-context.mjs'),
51+
'@': join(__dirname, '../'),
52+
},
53+
},
4354
// We need to configure `node:` APIs as Externals to WebPack
4455
// since essentially they're not supported on the browser
4556
externals: {
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<link rel="preconnect" href="https://fonts.googleapis.com" />
2+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
3+
4+
<link
5+
rel="stylesheet"
6+
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&family=Open+Sans:ital,wght@0,300..800;1,300..800"
7+
/>
8+
9+
<style>
10+
:root {
11+
color-scheme: light;
12+
13+
--font-ibm-plex-mono: 'IBM Plex Mono';
14+
--font-open-sans: 'Open Sans';
15+
}
16+
17+
html[data-theme='dark'] {
18+
color-scheme: dark;
19+
}
20+
</style>

apps/site/.storybook/preview.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ import { NextIntlClientProvider } from 'next-intl';
66
import { STORYBOOK_MODES, STORYBOOK_SIZES } from '@/.storybook/constants';
77
import { NotificationProvider } from '@/providers/notificationProvider';
88

9-
import '../next.fonts';
109
import '../styles/index.css';
1110

1211
const preview: Preview = {
1312
parameters: {
14-
nextjs: { router: { basePath: '' }, appDirectory: true },
1513
chromatic: { modes: STORYBOOK_MODES },
1614
viewport: { defaultViewport: 'large', viewports: STORYBOOK_SIZES },
1715
},
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* This file extends on the `page.tsx` file, which is the default file that is used to render
3+
* the entry points for each locale and then also reused within the [...path] route to render the
4+
* and contains all logic for rendering our dynamic and static routes within the Node.js Website.
5+
*
6+
* Note: that each `page.tsx` should have its own `generateStaticParams` to prevent clash of
7+
* dynamic params, which will lead on static export errors and other sort of issues.
8+
*/
9+
10+
import * as basePage from '@/app/[locale]/page';
11+
import { ENABLE_STATIC_EXPORT } from '@/next.constants.mjs';
12+
import { dynamicRouter } from '@/next.dynamic.mjs';
13+
import { availableLocaleCodes } from '@/next.locales.mjs';
14+
15+
// This is the default Viewport Metadata
16+
// @see https://nextjs.org/docs/app/api-reference/functions/generate-viewport#generateviewport-function
17+
export const generateViewport = basePage.generateViewport;
18+
19+
// This generates each page's HTML Metadata
20+
// @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata
21+
export const generateMetadata = basePage.generateMetadata;
22+
23+
// This provides all the possible paths that can be generated statically
24+
// + provides all the paths that we support on the Node.js Website
25+
export const generateStaticParams = async () => {
26+
const allAvailableRoutes = await Promise.all(
27+
// Gets all mapped routes to the Next.js Routing Engine by Locale
28+
availableLocaleCodes.map(async (locale: string) => {
29+
const routesForLanguage = await dynamicRouter.getRoutesByLanguage(locale);
30+
31+
return routesForLanguage.map(pathname =>
32+
dynamicRouter.mapPathToRoute(locale, pathname)
33+
);
34+
})
35+
);
36+
37+
return ENABLE_STATIC_EXPORT ? allAvailableRoutes.flat().sort() : [];
38+
};
39+
40+
// Enforces that this route is used as static rendering
41+
// Except whenever on the Development mode as we want instant-refresh when making changes
42+
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
43+
export const dynamic = 'force-static';
44+
45+
// Ensures that this endpoint is invalidated and re-executed every X minutes
46+
// so that when new deployments happen, the data is refreshed
47+
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
48+
export const revalidate = 300;
49+
50+
export default basePage.default;

apps/site/app/[locale]/error.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
'use client';
22

33
import { ArrowRightIcon } from '@heroicons/react/24/solid';
4-
import { captureException } from '@sentry/nextjs';
54
import { useTranslations } from 'next-intl';
65
import type { FC } from 'react';
76

87
import Button from '@/components/Common/Button';
98
import GlowingBackdropLayout from '@/layouts/GlowingBackdrop';
109

11-
const ErrorPage: FC<{ error: Error }> = ({ error }) => {
12-
captureException(error);
10+
const ErrorPage: FC<{ error: Error }> = () => {
1311
const t = useTranslations();
1412

1513
return (

apps/site/app/[locale]/feed/[feed]/route.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import { NextResponse } from 'next/server';
22

33
import provideWebsiteFeeds from '@/next-data/providers/websiteFeeds';
4-
import { VERCEL_REVALIDATE } from '@/next.constants.mjs';
54
import { siteConfig } from '@/next.json.mjs';
65
import { defaultLocale } from '@/next.locales.mjs';
76

87
// We only support fetching these pages from the /en/ locale code
98
const locale = defaultLocale.code;
109

11-
type StaticParams = { params: { feed: string; locale: string } };
10+
type StaticParams = { params: Promise<{ feed: string; locale: string }> };
1211

1312
// This is the Route Handler for the `GET` method which handles the request
1413
// for the Node.js Website Blog Feeds (RSS)
1514
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
16-
export const GET = async (_: Request, { params }: StaticParams) => {
15+
export const GET = async (_: Request, props: StaticParams) => {
16+
const params = await props.params;
17+
1718
// Generate the Feed for the given feed type (blog, releases, etc)
1819
const websiteFeed = provideWebsiteFeeds(params.feed);
1920

@@ -29,11 +30,9 @@ export const GET = async (_: Request, { params }: StaticParams) => {
2930
export const generateStaticParams = async () =>
3031
siteConfig.rssFeeds.map(feed => ({ feed: feed.file, locale }));
3132

32-
// In this case we want to catch-all possible requests. This is so that if a non defined feed is
33-
// requested we can manually return a 404 response for it instead of having Next.js handle it
34-
// and return our top level custom 404 html page instead
33+
// Enforces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary
3534
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams
36-
export const dynamicParams = true;
35+
export const dynamicParams = false;
3736

3837
// Enforces that this route is cached and static as much as possible
3938
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
@@ -42,4 +41,4 @@ export const dynamic = 'force-static';
4241
// Ensures that this endpoint is invalidated and re-executed every X minutes
4342
// so that when new deployments happen, the data is refreshed
4443
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
45-
export const revalidate = VERCEL_REVALIDATE;
44+
export const revalidate = 300;

apps/site/app/[locale]/layout.tsx

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Analytics } from '@vercel/analytics/react';
22
import { SpeedInsights } from '@vercel/speed-insights/next';
33
import classNames from 'classnames';
4-
import { getLocale } from 'next-intl/server';
54
import type { FC, PropsWithChildren } from 'react';
65

76
import BaseLayout from '@/layouts/Base';
@@ -15,13 +14,20 @@ import '@/styles/index.css';
1514

1615
const fontClasses = classNames(IBM_PLEX_MONO.variable, OPEN_SANS.variable);
1716

18-
const RootLayout: FC<PropsWithChildren> = async ({ children }) => {
19-
const locale = await getLocale();
17+
type RotLayoutProps = PropsWithChildren<{ params: { locale: string } }>;
18+
19+
const RootLayout: FC<RotLayoutProps> = async ({ children, params }) => {
20+
const { locale } = await params;
2021

2122
const { langDir, hrefLang } = availableLocalesMap[locale] || defaultLocale;
2223

2324
return (
24-
<html className={fontClasses} dir={langDir} lang={hrefLang}>
25+
<html
26+
className={fontClasses}
27+
dir={langDir}
28+
lang={hrefLang}
29+
suppressHydrationWarning
30+
>
2531
<body suppressHydrationWarning>
2632
<LocaleProvider>
2733
<ThemeProvider>

apps/site/app/[locale]/next-data/api-data/route.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { deflateSync } from 'node:zlib';
22

33
import provideReleaseData from '@/next-data/providers/releaseData';
4-
import { GITHUB_API_KEY, VERCEL_REVALIDATE } from '@/next.constants.mjs';
4+
import { GITHUB_API_KEY } from '@/next.constants.mjs';
55
import { defaultLocale } from '@/next.locales.mjs';
66
import type { GitHubApiFile } from '@/types';
77
import { getGitHubApiDocsUrl } from '@/util/gitHubUtils';
@@ -29,14 +29,16 @@ export const GET = async () => {
2929

3030
const gitHubApiResponse = await fetch(
3131
getGitHubApiDocsUrl(versionWithPrefix),
32-
authorizationHeaders
32+
{ ...authorizationHeaders, cache: 'force-cache' }
3333
);
3434

3535
return gitHubApiResponse.json().then((apiDocsFiles: Array<GitHubApiFile>) => {
3636
// maps over each api file and get the download_url, fetch the content and deflates it
3737
const mappedApiFiles = apiDocsFiles.map(
3838
async ({ name, path: filename, download_url }) => {
39-
const apiFileResponse = await fetch(download_url);
39+
const apiFileResponse = await fetch(download_url, {
40+
cache: 'force-cache',
41+
});
4042

4143
// Retrieves the content as a raw text string
4244
const source = await apiFileResponse.text();
@@ -72,9 +74,9 @@ export const dynamicParams = false;
7274

7375
// Enforces that this route is used as static rendering
7476
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
75-
export const dynamic = 'error';
77+
export const dynamic = 'force-static';
7678

7779
// Ensures that this endpoint is invalidated and re-executed every X minutes
7880
// so that when new deployments happen, the data is refreshed
7981
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
80-
export const revalidate = VERCEL_REVALIDATE;
82+
export const revalidate = 300;
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import {
2-
provideBlogCategories,
32
provideBlogPosts,
43
providePaginatedBlogPosts,
54
} from '@/next-data/providers/blogData';
6-
import { VERCEL_REVALIDATE } from '@/next.constants.mjs';
75
import { defaultLocale } from '@/next.locales.mjs';
86

97
type StaticParams = {
10-
params: { locale: string; category: string; page: string };
8+
params: Promise<{ locale: string; category: string; page: string }>;
119
};
1210

1311
// This is the Route Handler for the `GET` method which handles the request
1412
// for providing Blog Posts for Blog Categories and Pagination Metadata
1513
// @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers
16-
export const GET = async (_: Request, { params }: StaticParams) => {
14+
export const GET = async (_: Request, props: StaticParams) => {
15+
const params = await props.params;
16+
1717
const requestedPage = Number(params.page);
1818

1919
const data =
@@ -27,41 +27,18 @@ export const GET = async (_: Request, { params }: StaticParams) => {
2727
};
2828

2929
// This function generates the static paths that come from the dynamic segments
30-
// `[locale]/next-data/blog-data/[category]` and returns an array of all available static paths
31-
// This is used for ISR static validation and generation
32-
export const generateStaticParams = async () => {
33-
// This metadata is the original list of all available categories and all available years
34-
// within the Node.js Website Blog Posts (2011, 2012...)
35-
const categories = provideBlogCategories();
36-
37-
const mappedCategories = categories.map(category => {
38-
// gets the current pagination meta for a given category
39-
const { pagination } = provideBlogPosts(category);
40-
41-
// creates a sequential array containing each page number
42-
const pages = [...Array(pagination.pages).keys()].map((_, key) => key + 1);
43-
44-
// maps the data into valid Next.js Route Engine routes with all required params
45-
// notice that we add an extra 0 in the beginning in case we want a non-paginated route
46-
return [0, ...pages].map(page => ({
47-
locale: defaultLocale.code,
48-
page: String(page),
49-
category,
50-
}));
51-
});
52-
53-
return mappedCategories.flat();
54-
};
55-
56-
// Enforces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary
57-
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams
58-
export const dynamicParams = false;
30+
// `[locale]/next-data/blog-data/[category]/[page]` this will return a default value as we don't want to
31+
// statically generate this route as it is compute-expensive.
32+
// Hence we generate a "fake" OG image during build just to satisfy Next.js requirements.
33+
export const generateStaticParams = async () => [
34+
{ locale: defaultLocale.code, category: 'all', page: '0' },
35+
];
5936

6037
// Enforces that this route is cached and static as much as possible
6138
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic
62-
export const dynamic = 'error';
39+
export const dynamic = 'force-static';
6340

6441
// Ensures that this endpoint is invalidated and re-executed every X minutes
6542
// so that when new deployments happen, the data is refreshed
6643
// @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
67-
export const revalidate = VERCEL_REVALIDATE;
44+
export const revalidate = 300;

0 commit comments

Comments
 (0)