Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[Parent Branch] Next js Pages Router to App Router migration #1146

Closed
wants to merge 12 commits into from
Closed
17 changes: 17 additions & 0 deletions app/ThemeRegistry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { AppRouterCacheProvider } from '@mui/material-nextjs/v13-appRouter';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import theme from '../styles/theme';

// This implementation is from mui integrations with nextjs app router
// see https://mui.com/material-ui/integrations/nextjs/#app-router
export default function ThemeRegistry({ children }: { children: React.ReactNode }) {
return (
<AppRouterCacheProvider>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</AppRouterCacheProvider>
);
}
8 changes: 8 additions & 0 deletions app/error-route-test/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use client';

export default function Page() {
setTimeout(() => {
throw new Error('An Error');
}, 1000);
return <div>ERROR TESTING PAGE</div>;
}
36 changes: 36 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Analytics from '../components/head/Analytics';
import GoogleTagManagerScript from '../components/head/GoogleTagManagerScript';
import RollbarScript from '../components/head/RollbarScript';
import ErrorBoundary from '../components/layout/ErrorBoundary';
import { newRelicInit } from '../config/newRelic';
import rootMetadata from './rootMetadata';
import ThemeRegistry from './ThemeRegistry';

export const metadata = rootMetadata;

export default async function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}: {
children: React.ReactNode;
}) {
const NewRelicScript = await newRelicInit();
return (
<html lang="en">
<body>
{/*
We should be using next third party library https://nextjs.org/docs/app/building-your-application/optimizing/third-party-libraries#google-tag-manager
but sending an event using sendGTMEvent requires an object rather than a list of arguments so the current gtag api function would need to be adapted
*/}
<GoogleTagManagerScript />
<RollbarScript />
<ErrorBoundary>
<ThemeRegistry>{children}</ThemeRegistry>
</ErrorBoundary>
{NewRelicScript}
<Analytics />
</body>
</html>
);
}
3 changes: 3 additions & 0 deletions app/public-route-test/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default async function Page() {
return <div>PUBLIC TESTING PAGE</div>;
}
32 changes: 32 additions & 0 deletions app/rootMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Metadata } from 'next';
import { PRIMARY_MAIN_COLOR } from '../constants/common';

const descriptionContent =
'Join us on your healing journey. Bloom is here for you to learn, heal and grow towards a confident future. It is bought to you by Chayn, a global non-profit, run by survivors and allies from around the world.';
const twitterDescriptionContent =
'Join us on your healing journey. Bloom is here for you to learn, heal and grow towards a confident future. It is bought to you by Chayn, a global non-profit, run by survivors and allies from around the world.';
const imageAltContent =
'An cartoon drawing of a person with almost shoulder length hair against a pink background. They have flowers and leaves coming out of their head. The word "Bloom" hovers above the person.';

// Nextjs automatically includes for each route two default meta tags, charset and viewport
// https://nextjs.org/docs/app/building-your-application/optimizing/metadata#default-fields
const rootMetadata: Metadata = {
title: 'Bloom',
openGraph: {
title: 'Welcome to Bloom',
description: descriptionContent,
images: [{ url: '/preview.png', alt: imageAltContent }],
},
twitter: {
description: twitterDescriptionContent,
card: 'summary_large_image',
images: [],
},
manifest: '/manifest.json',
icons: [{ rel: 'apple-touch-icon', url: '/icons/apple/icon-120x120.png' }],
other: {
'theme-color': PRIMARY_MAIN_COLOR,
},
};

export default rootMetadata;
13 changes: 13 additions & 0 deletions components/head/Analytics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Analytics as VercelAnalytics } from '@vercel/analytics/react';
import { Hotjar } from 'nextjs-hotjar';

export default function Analytics() {
return (
<>
{!!process.env.NEXT_PUBLIC_HOTJAR_ID && process.env.NEXT_PUBLIC_ENV !== 'local' && (
<Hotjar id={process.env.NEXT_PUBLIC_HOTJAR_ID} sv={6} strategy="lazyOnload" />
)}
<VercelAnalytics />
</>
);
}
2 changes: 1 addition & 1 deletion components/head/GoogleTagManagerScript.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const GoogleTagManagerScript = () => {
return (
<Script
id="gtag"
strategy="worker"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
Expand Down
7 changes: 6 additions & 1 deletion components/head/RollbarScript.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import Script from 'next/script';

const RollbarScript = () => {
return (
<script
// eslint-disable-next-line @next/next/no-before-interactive-script-outside-document
<Script
id="rollbar"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `
var _rollbarConfig = {
Expand Down
2 changes: 2 additions & 0 deletions components/layout/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import { Alert, Snackbar } from '@mui/material';
import { Component, ErrorInfo, ReactNode } from 'react';

Expand Down
46 changes: 46 additions & 0 deletions config/newRelic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import newrelic from 'newrelic';
import Script from 'next/script';

// Configuration according to Newrelic app router example
// See https://github.com/newrelic/newrelic-node-nextjs?tab=readme-ov-file#example-projects
export const newRelicInit = async () => {
// @ts-ignore
if (newrelic.agent.collector.isConnected() === false) {
await new Promise((resolve) => {
// @ts-ignore
newrelic.agent.on('connected', resolve);
});
}

const browserTimingHeader = newrelic.getBrowserTimingHeader({
hasToRemoveScriptWrapper: true,
// @ts-ignore
allowTransactionlessInjection: true,
});

return <NewRelicScript browserTimingHeader={browserTimingHeader} />;
};

export type NewRelicScriptProps = {
browserTimingHeader: string;
};

const NewRelicScript = ({ browserTimingHeader }: NewRelicScriptProps) => {
return (
// eslint-disable-next-line @next/next/no-before-interactive-script-outside-document
<Script
// We have to set an id for inline scripts.
// See https://nextjs.org/docs/app/building-your-application/optimizing/scripts#inline-scripts
id="nr-browser-agent"
// By setting the strategy to "beforeInteractive" we guarantee that
// the script will be added to the document's `head` element.
strategy="beforeInteractive"
// The body of the script element comes from the async evaluation
// of `getInitialProps`. We use the special
// `dangerouslySetInnerHTML` to provide that element body. Since
// it requires an object with an `__html` property, we pass in an
// object literal.
dangerouslySetInnerHTML={{ __html: browserTimingHeader }}
/>
);
};
2 changes: 2 additions & 0 deletions constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export type ErrorDisplay =
| ReactElement<any, string | JSXElementConstructor<any>>
| ReactNodeArray
| null;

export const PRIMARY_MAIN_COLOR = '#F3D6D8';
1 change: 1 addition & 0 deletions next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
18 changes: 18 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,26 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});

// Configuration according to Newrelic app router example
// See https://github.com/newrelic/newrelic-node-nextjs?tab=readme-ov-file#example-projects
const nrExternals = require('@newrelic/next/load-externals');

module.exports = withBundleAnalyzer(
withPWA({
experimental: {
// Without this setting, the Next.js compilation step will routinely
// try to import files such as `LICENSE` from the `newrelic` module.
// See https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages.
serverComponentsExternalPackages: ['newrelic'],
},

// In order for newrelic to effectively instrument a Next.js application,
// the modules that newrelic supports should not be mangled by webpack. Thus,
// we need to "externalize" all of the modules that newrelic supports.
webpack: (config) => {
nrExternals(config);
return config;
},
reactStrictMode: true,
publicRuntimeConfig: {
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@mui/icons-material": "^5.16.0",
"@mui/lab": "^5.0.0-alpha.171",
"@mui/material": "^5.16.1",
"@mui/material-nextjs": "^6.0.2",
"@newrelic/next": "^0.10.0",
"@reduxjs/toolkit": "^2.2.7",
"@storyblok/react": "^3.0.0",
Expand Down
4 changes: 4 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { Hotjar } from 'nextjs-hotjar';
import { useEffect } from 'react';
import { Provider } from 'react-redux';
import CrispScript from '../components/crisp/CrispScript';
import GoogleTagManagerScript from '../components/head/GoogleTagManagerScript';
import RollbarScript from '../components/head/RollbarScript';
import Consent from '../components/layout/Consent';
import ErrorBoundary from '../components/layout/ErrorBoundary';
import Footer from '../components/layout/Footer';
Expand Down Expand Up @@ -76,6 +78,8 @@ function MyApp(props: MyAppProps) {
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<GoogleTagManagerScript />
<RollbarScript />
<CrispScript />
<ThemeProvider theme={theme}>
<CssBaseline />
Expand Down
4 changes: 0 additions & 4 deletions pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import newrelic from 'newrelic';
import { AppType } from 'next/app';
import Document, { Head, Html, Main, NextScript } from 'next/document';
import * as React from 'react';
import GoogleTagManagerScript from '../components/head/GoogleTagManagerScript';
import OpenGraphMetadata from '../components/head/OpenGraphMetadata';
import RollbarScript from '../components/head/RollbarScript';
import createEmotionCache from '../config/emotionCache';
import { MyAppProps } from './_app';

Expand All @@ -27,8 +25,6 @@ export default class MyDocument extends Document<NewRelicProps> {
rel="stylesheet"
/>
<OpenGraphMetadata />
<GoogleTagManagerScript />
<RollbarScript />
</Head>
<body>
<Main />
Expand Down
5 changes: 4 additions & 1 deletion styles/theme.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
'use client';

import { createTheme, lighten, responsiveFontSizes } from '@mui/material/styles';
import { PRIMARY_MAIN_COLOR } from '../constants/common';

// If you want to declare custom colours that aren't officially in the palette, add them here
declare module '@mui/material/styles' {
Expand All @@ -16,7 +19,7 @@ declare module '@mui/material/styles' {
let theme = createTheme({
palette: {
primary: {
main: '#F3D6D8',
main: PRIMARY_MAIN_COLOR,
light: '#F7E2E4',
dark: '#EA0050',
},
Expand Down
35 changes: 35 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,13 @@
dependencies:
regenerator-runtime "^0.14.0"

"@babel/runtime@^7.25.0":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2"
integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==
dependencies:
regenerator-runtime "^0.14.0"

"@babel/template@^7.24.7", "@babel/template@^7.3.3":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315"
Expand Down Expand Up @@ -1280,6 +1287,17 @@
"@emotion/weak-memoize" "^0.4.0"
stylis "4.2.0"

"@emotion/cache@^11.13.1":
version "11.13.1"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7"
integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==
dependencies:
"@emotion/memoize" "^0.9.0"
"@emotion/sheet" "^1.4.0"
"@emotion/utils" "^1.4.0"
"@emotion/weak-memoize" "^0.4.0"
stylis "4.2.0"

"@emotion/hash@^0.9.1":
version "0.9.1"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43"
Expand Down Expand Up @@ -1342,6 +1360,11 @@
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c"
integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==

"@emotion/sheet@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c"
integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==

"@emotion/styled@^11.11.5":
version "11.11.5"
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.5.tgz#0c5c8febef9d86e8a926e663b2e5488705545dfb"
Expand Down Expand Up @@ -1369,6 +1392,11 @@
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.0.tgz#262f1d02aaedb2ec91c83a0955dd47822ad5fbdd"
integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==

"@emotion/utils@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.0.tgz#262f1d02aaedb2ec91c83a0955dd47822ad5fbdd"
integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==

"@emotion/weak-memoize@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
Expand Down Expand Up @@ -2233,6 +2261,13 @@
clsx "^2.1.0"
prop-types "^15.8.1"

"@mui/material-nextjs@^6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@mui/material-nextjs/-/material-nextjs-6.0.2.tgz#db2d27963803f905f10a6810915eca9aaf354e67"
integrity sha512-P5ZZ6P2UXstuW746J9uLkwAHIB/HKnjWcn4I4kF8uFinGpIbEt8BC58BCSqlfesB/pHNxJqaSPWDB4wBBFc42g==
dependencies:
"@babel/runtime" "^7.25.0"

"@mui/material@^5.16.1":
version "5.16.1"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.16.1.tgz#6fcef9b5709df5864cf0b0bc0ea7b453a9d9e420"
Expand Down
Loading