Skip to content
New issue

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

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

Already on GitHub? Sign in 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