Skip to content

Commit

Permalink
feat(nextjs): Add instructions on how to add a global-error page to…
Browse files Browse the repository at this point in the history
… Next.js App Router (#506)
  • Loading branch information
lforst authored Nov 29, 2023
1 parent 109f599 commit 249a0c5
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 19 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Unreleased

- feat(nextjs): Add instructions on how to add a `global-error` page to Next.js
App Router (#506)

## 3.17.0

- feat(reactnative): Use Xcode scripts bundled with Sentry RN SDK (#499)
Expand Down
118 changes: 99 additions & 19 deletions src/nextjs/nextjs-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ import {
import { SentryProjectData, WizardOptions } from '../utils/types';
import {
getFullUnderscoreErrorCopyPasteSnippet,
getGlobalErrorCopyPasteSnippet,
getNextjsConfigCjsAppendix,
getNextjsConfigCjsTemplate,
getNextjsConfigEsmCopyPasteSnippet,
getNextjsSentryBuildOptionsTemplate,
getNextjsWebpackPluginOptionsTemplate,
getSentryConfigContents,
getSentryDefaultGlobalErrorPage,
getSentryDefaultUnderscoreErrorPage,
getSentryExampleApiRoute,
getSentryExampleAppDirApiRoute,
Expand Down Expand Up @@ -124,7 +126,7 @@ export async function runNextjsWizardWithTelemetry(
);

clack.log.success(
`Created ${chalk.bold(path.join(...pagesLocation, '_error.jsx'))}.`,
`Created ${chalk.cyan(path.join(...pagesLocation, '_error.jsx'))}.`,
);
} else if (
fs
Expand All @@ -147,7 +149,7 @@ export async function runNextjsWizardWithTelemetry(

const shouldContinue = await abortIfCancelled(
clack.confirm({
message: `Did you modify your ${chalk.bold(
message: `Did you modify your ${chalk.cyan(
path.join(...pagesLocation, underscoreErrorPageFile),
)} file as described above?`,
active: 'Yes',
Expand All @@ -160,7 +162,7 @@ export async function runNextjsWizardWithTelemetry(
}
} else {
clack.log.info(
`It seems like you already have a custom error page.\n\nPlease add the following code to your custom error page\nat ${chalk.bold(
`It seems like you already have a custom error page.\n\nPlease add the following code to your custom error page\nat ${chalk.cyan(
path.join(...pagesLocation, underscoreErrorPageFile),
)}:`,
);
Expand All @@ -175,7 +177,7 @@ export async function runNextjsWizardWithTelemetry(

const shouldContinue = await abortIfCancelled(
clack.confirm({
message: `Did add the code to your ${chalk.bold(
message: `Did add the code to your ${chalk.cyan(
path.join(...pagesLocation, underscoreErrorPageFile),
)} file as described above?`,
active: 'Yes',
Expand All @@ -189,6 +191,84 @@ export async function runNextjsWizardWithTelemetry(
}
});

await traceStep('create-global-error-page', async () => {
const maybeAppDirPath = path.join(process.cwd(), 'pages');
const maybeSrcAppDirPath = path.join(process.cwd(), 'src', 'pages');

const appDirLocation =
fs.existsSync(maybeAppDirPath) &&
fs.lstatSync(maybeAppDirPath).isDirectory()
? ['app']
: fs.existsSync(maybeSrcAppDirPath) &&
fs.lstatSync(maybeSrcAppDirPath).isDirectory()
? ['src', 'app']
: undefined;

if (!appDirLocation) {
return;
}

const globalErrorPageFile = fs.existsSync(
path.join(process.cwd(), ...appDirLocation, 'global-error.tsx'),
)
? 'global-error.tsx'
: fs.existsSync(
path.join(process.cwd(), ...appDirLocation, 'global-error.ts'),
)
? 'global-error.ts'
: fs.existsSync(
path.join(process.cwd(), ...appDirLocation, 'global-error.jsx'),
)
? 'global-error.jsx'
: fs.existsSync(
path.join(process.cwd(), ...appDirLocation, 'global-error.js'),
)
? 'global-error.js'
: undefined;

if (!globalErrorPageFile) {
await fs.promises.writeFile(
path.join(process.cwd(), ...appDirLocation, 'global-error.jsx'),
getSentryDefaultGlobalErrorPage(),
{ encoding: 'utf8', flag: 'w' },
);

clack.log.success(
`Created ${chalk.cyan(
path.join(...appDirLocation, 'global-error.jsx'),
)}.`,
);
} else {
clack.log.info(
`It seems like you already have a custom error page for your app directory.\n\nPlease add the following code to your custom error page\nat ${chalk.cyan(
path.join(...appDirLocation, globalErrorPageFile),
)}:`,
);

// eslint-disable-next-line no-console
console.log(
getGlobalErrorCopyPasteSnippet(
globalErrorPageFile === 'global-error.ts' ||
globalErrorPageFile === 'global-error.tsx',
),
);

const shouldContinue = await abortIfCancelled(
clack.confirm({
message: `Did add the code to your ${chalk.cyan(
path.join(...appDirLocation, globalErrorPageFile),
)} file as described above?`,
active: 'Yes',
inactive: 'No, get me out of here',
}),
);

if (!shouldContinue) {
await abort();
}
}
});

await traceStep('create-example-page', async () =>
createExamplePage(selfHosted, selectedProject, sentryUrl),
);
Expand Down Expand Up @@ -265,11 +345,11 @@ async function createOrMergeNextJsFiles(
if (overwriteExistingConfigs) {
if (jsConfigExists) {
fs.unlinkSync(path.join(process.cwd(), jsConfig));
clack.log.warn(`Removed existing ${chalk.bold(jsConfig)}.`);
clack.log.warn(`Removed existing ${chalk.cyan(jsConfig)}.`);
}
if (tsConfigExists) {
fs.unlinkSync(path.join(process.cwd(), tsConfig));
clack.log.warn(`Removed existing ${chalk.bold(tsConfig)}.`);
clack.log.warn(`Removed existing ${chalk.cyan(tsConfig)}.`);
}
}
}
Expand All @@ -284,7 +364,7 @@ async function createOrMergeNextJsFiles(
{ encoding: 'utf8', flag: 'w' },
);
clack.log.success(
`Created fresh ${chalk.bold(
`Created fresh ${chalk.cyan(
typeScriptDetected ? tsConfig : jsConfig,
)}.`,
);
Expand Down Expand Up @@ -325,7 +405,7 @@ async function createOrMergeNextJsFiles(
);

clack.log.success(
`Created ${chalk.bold('next.config.js')} with Sentry configuration.`,
`Created ${chalk.cyan('next.config.js')} with Sentry configuration.`,
);
}

Expand All @@ -346,7 +426,7 @@ async function createOrMergeNextJsFiles(
if (probablyIncludesSdk) {
const injectAnyhow = await abortIfCancelled(
clack.confirm({
message: `${chalk.bold(
message: `${chalk.cyan(
nextConfigJs,
)} already contains Sentry SDK configuration. Should the wizard modify it anyways?`,
}),
Expand All @@ -366,7 +446,7 @@ async function createOrMergeNextJsFiles(
);

clack.log.success(
`Added Sentry configuration to ${chalk.bold(
`Added Sentry configuration to ${chalk.cyan(
nextConfigJs,
)}. ${chalk.dim('(you probably want to clean this up a bit!)')}`,
);
Expand All @@ -390,7 +470,7 @@ async function createOrMergeNextJsFiles(
if (probablyIncludesSdk) {
const injectAnyhow = await abortIfCancelled(
clack.confirm({
message: `${chalk.bold(
message: `${chalk.cyan(
nextConfigMjs,
)} already contains Sentry SDK configuration. Should the wizard modify it anyways?`,
}),
Expand Down Expand Up @@ -426,7 +506,7 @@ async function createOrMergeNextJsFiles(
},
);
clack.log.success(
`Added Sentry configuration to ${chalk.bold(
`Added Sentry configuration to ${chalk.cyan(
nextConfigMjs,
)}. ${chalk.dim('(you probably want to clean this up a bit!)')}`,
);
Expand All @@ -437,11 +517,11 @@ async function createOrMergeNextJsFiles(
Sentry.setTag('next-config-mod-result', 'fail');
clack.log.warn(
chalk.yellow(
`Something went wrong writing to ${chalk.bold(nextConfigMjs)}`,
`Something went wrong writing to ${chalk.cyan(nextConfigMjs)}`,
),
);
clack.log.info(
`Please put the following code snippet into ${chalk.bold(
`Please put the following code snippet into ${chalk.cyan(
nextConfigMjs,
)}: ${chalk.dim('You probably have to clean it up a bit.')}\n`,
);
Expand All @@ -456,7 +536,7 @@ async function createOrMergeNextJsFiles(

const shouldContinue = await abortIfCancelled(
clack.confirm({
message: `Are you done putting the snippet above into ${chalk.bold(
message: `Are you done putting the snippet above into ${chalk.cyan(
nextConfigMjs,
)}?`,
active: 'Yes',
Expand Down Expand Up @@ -541,7 +621,7 @@ async function createExamplePage(
);

clack.log.success(
`Created ${chalk.bold(
`Created ${chalk.cyan(
path.join(...appLocation, 'sentry-example-page', 'page.jsx'),
)}.`,
);
Expand All @@ -566,7 +646,7 @@ async function createExamplePage(
);

clack.log.success(
`Created ${chalk.bold(
`Created ${chalk.cyan(
path.join(...appLocation, 'api', 'sentry-example-api', 'route.js'),
)}.`,
);
Expand All @@ -586,7 +666,7 @@ async function createExamplePage(
);

clack.log.success(
`Created ${chalk.bold(
`Created ${chalk.cyan(
path.join(...pagesLocation, 'sentry-example-page.js'),
)}.`,
);
Expand All @@ -607,7 +687,7 @@ async function createExamplePage(
);

clack.log.success(
`Created ${chalk.bold(
`Created ${chalk.cyan(
path.join(...pagesLocation, 'api', 'sentry-example-api.js'),
)}.`,
);
Expand Down
71 changes: 71 additions & 0 deletions src/nextjs/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,74 @@ YourCustomErrorComponent.getInitialProps = async (contextData${
};
`;
}

export function getSentryDefaultGlobalErrorPage() {
return `"use client";
import * as Sentry from "@sentry/nextjs";
import Error from "next/error";
import { useEffect } from "react";
export default function GlobalError({ error }) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);
return (
<html>
<body>
<Error />
</body>
</html>
);
}
`;
}

export function getGlobalErrorCopyPasteSnippet(isTs: boolean) {
if (isTs) {
return `"use client";
${chalk.green('import * as Sentry from "@sentry/nextjs";')}
${chalk.green('import Error from "next/error";')}
${chalk.green('import { useEffect } from "react";')}
export default function GlobalError(${chalk.green(
'{ error }: { error: Error }',
)}) {
${chalk.green(`useEffect(() => {
Sentry.captureException(error);
}, [error]);`)}
return (
<html>
<body>
{/* Your Error component here... */}
</body>
</html>
);
}
`;
} else {
return `"use client";
${chalk.green('import * as Sentry from "@sentry/nextjs";')}
${chalk.green('import Error from "next/error";')}
${chalk.green('import { useEffect } from "react";')}
export default function GlobalError(${chalk.green('{ error }')}) {
${chalk.green(`useEffect(() => {
Sentry.captureException(error);
}, [error]);`)}
return (
<html>
<body>
{/* Your Error component here... */}
</body>
</html>
);
}
`;
}
}

0 comments on commit 249a0c5

Please sign in to comment.