Skip to content

Commit

Permalink
feat(nextjs): Add instructions for custom _error page (#496)
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst authored Nov 14, 2023
1 parent 959fbd5 commit 437a55b
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 10 deletions.
33 changes: 23 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Changelog

## Unreleased

- feat(nextjs): Add instructions for custom \_error page (#496)

## 3.16.3

- fix(sourcemaps): Re-read package.json when modifying build command #493
- fix(sourcemaps): Re-read package.json when modifying build command (#493)

## 3.16.2

Expand All @@ -15,10 +19,13 @@

## 3.16.0

- ref(reactnative): Use clack prompts and share common code (dirty repo, login) (#473)
- ref(reactnative): Use clack prompts and share common code (dirty repo, login)
(#473)
- feat(reactnative): Add telemetry (#477)
- feat(reactnative): Improve `build.gradle` patch so that it's more likely to work without changes in monorepos (#478)
- fix(reactnative): Save Sentry URL, Organization and Project to `sentry.properties` (#479)
- feat(reactnative): Improve `build.gradle` patch so that it's more likely to
work without changes in monorepos (#478)
- fix(reactnative): Save Sentry URL, Organization and Project to
`sentry.properties` (#479)

## 3.15.0

Expand All @@ -42,14 +49,17 @@

- enh(android): Show link to issues page after setup is complete (#448)
- feat(remix): Pass `org`, `project`, `url` to `upload-sourcemaps` script (#434)
- feat(sourcemaps): Automatically enable source maps generation in `tsconfig.json` (#449)
- feat(sourcemaps): Automatically enable source maps generation in
`tsconfig.json` (#449)
- feat(sveltekit): Add telemetry collection (#455)
- fix(nextjs): Add selfhosted url in `next.config.js` (#438)
- fix(nextjs): Create necessary directories in app router (#439)
- fix(sourcemaps): Write package manager command instead of object to package.json (#453)
- fix(sourcemaps): Write package manager command instead of object to
package.json (#453)
- ref(sveltekit): Check for minimum supported SvelteKit version (#456)

Work in this release contributed by @andreysam. Thank you for your contributions!
Work in this release contributed by @andreysam. Thank you for your
contributions!

## 3.12.0

Expand Down Expand Up @@ -81,16 +91,19 @@ brew install getsentry/tools/sentry-wizard
```

- feat: Add Bun package manager support (#417)
- feat(apple): Add option to choose between cocoapods when available and SPM (#423)
- feat(apple): Add option to choose between cocoapods when available and SPM
(#423)
- feat(apple): Search App entry point by build files not directories (#420)
- feat(apple): Use ".sentryclirc" for auth instead of hard coding it (#422)
- feat(nextjs): Add support for Next.js 13 app router (#385)
- feat(sourcemaps): Provide exit path if there's no need to upload sourcemaps (#415)
- feat(sourcemaps): Provide exit path if there's no need to upload sourcemaps
(#415)
- fix: Handle no projects available (#412)
- fix: Remove picocolor usage (#426)
- fix: Support org auth tokens in old wizards (#409)
- fix: Treat user-entered DSN as a public DSN (#410)
- fix(sourcemaps): Enable source map generation when modifying Vite config (#421)
- fix(sourcemaps): Enable source map generation when modifying Vite config
(#421)

## 3.10.0

Expand Down
106 changes: 106 additions & 0 deletions src/nextjs/nextjs-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ import {
} from '../utils/clack-utils';
import { SentryProjectData, WizardOptions } from '../utils/types';
import {
getFullUnderscoreErrorCopyPasteSnippet,
getNextjsConfigCjsAppendix,
getNextjsConfigCjsTemplate,
getNextjsConfigEsmCopyPasteSnippet,
getNextjsSentryBuildOptionsTemplate,
getNextjsWebpackPluginOptionsTemplate,
getSentryConfigContents,
getSentryDefaultUnderscoreErrorPage,
getSentryExampleApiRoute,
getSentryExampleAppDirApiRoute,
getSentryExamplePageContents,
getSimpleUnderscoreErrorCopyPasteSnippet,
} from './templates';
import { traceStep, withTelemetry } from '../telemetry';
import { getPackageVersion, hasPackageInstalled } from '../utils/package-json';
Expand Down Expand Up @@ -83,6 +86,109 @@ export async function runNextjsWizardWithTelemetry(
createOrMergeNextJsFiles(selectedProject, selfHosted, sentryUrl),
);

await traceStep('create-underscoreerror-page', async () => {
const srcDir = path.join(process.cwd(), 'src');
const maybePagesDirPath = path.join(process.cwd(), 'pages');
const maybeSrcPagesDirPath = path.join(srcDir, 'pages');

const pagesLocation =
fs.existsSync(maybePagesDirPath) &&
fs.lstatSync(maybePagesDirPath).isDirectory()
? ['pages']
: fs.existsSync(maybeSrcPagesDirPath) &&
fs.lstatSync(maybeSrcPagesDirPath).isDirectory()
? ['src', 'pages']
: undefined;

if (!pagesLocation) {
return;
}

const underscoreErrorPageFile = fs.existsSync(
path.join(process.cwd(), ...pagesLocation, '_error.tsx'),
)
? '_error.tsx'
: fs.existsSync(path.join(process.cwd(), ...pagesLocation, '_error.ts'))
? '_error.ts'
: fs.existsSync(path.join(process.cwd(), ...pagesLocation, '_error.jsx'))
? '_error.jsx'
: fs.existsSync(path.join(process.cwd(), ...pagesLocation, '_error.js'))
? '_error.js'
: undefined;

if (!underscoreErrorPageFile) {
await fs.promises.writeFile(
path.join(process.cwd(), ...pagesLocation, '_error.jsx'),
getSentryDefaultUnderscoreErrorPage(),
{ encoding: 'utf8', flag: 'w' },
);

clack.log.success(
`Created ${chalk.bold(path.join(...pagesLocation, '_error.jsx'))}.`,
);
} else if (
fs
.readFileSync(
path.join(process.cwd(), ...pagesLocation, underscoreErrorPageFile),
'utf8',
)
.includes('getInitialProps')
) {
clack.log.info(
`It seems like you already have a custom error page.\n\nPlease put the following function call in the ${chalk.bold(
'getInitialProps',
)}\nmethod of your custom error page at ${chalk.bold(
path.join(...pagesLocation, underscoreErrorPageFile),
)}:`,
);

// eslint-disable-next-line no-console
console.log(getSimpleUnderscoreErrorCopyPasteSnippet());

const shouldContinue = await abortIfCancelled(
clack.confirm({
message: `Did you modify your ${chalk.bold(
path.join(...pagesLocation, underscoreErrorPageFile),
)} file as described above?`,
active: 'Yes',
inactive: 'No, get me out of here',
}),
);

if (!shouldContinue) {
await abort();
}
} 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(
path.join(...pagesLocation, underscoreErrorPageFile),
)}:`,
);

// eslint-disable-next-line no-console
console.log(
getFullUnderscoreErrorCopyPasteSnippet(
underscoreErrorPageFile === '_error.ts' ||
underscoreErrorPageFile === '_error.tsx',
),
);

const shouldContinue = await abortIfCancelled(
clack.confirm({
message: `Did add the code to your ${chalk.bold(
path.join(...pagesLocation, underscoreErrorPageFile),
)} 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
57 changes: 57 additions & 0 deletions src/nextjs/templates.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import chalk from 'chalk';

export function getNextjsWebpackPluginOptionsTemplate(
orgSlug: string,
projectSlug: string,
Expand Down Expand Up @@ -267,3 +269,58 @@ export function GET() {
}
`;
}

export function getSentryDefaultUnderscoreErrorPage() {
return `import * as Sentry from "@sentry/nextjs";
import Error from "next/error";
const CustomErrorComponent = (props) => {
return <Error statusCode={props.statusCode} />;
};
CustomErrorComponent.getInitialProps = async (contextData) => {
// In case this is running in a serverless function, await this in order to give Sentry
// time to send the error before the lambda exits
await Sentry.captureUnderscoreErrorException(contextData);
// This will contain the status code of the response
return Error.getInitialProps(contextData);
};
export default CustomErrorComponent;
`;
}

export function getSimpleUnderscoreErrorCopyPasteSnippet() {
return `
${chalk.green(`import * as Sentry from '@sentry/nextjs';`)}
${chalk.dim(
'// Replace "YourCustomErrorComponent" with your custom error component!',
)}
YourCustomErrorComponent.getInitialProps = async (${chalk.green(
`contextData`,
)}) => {
${chalk.green('await Sentry.captureUnderscoreErrorException(contextData);')}
${chalk.dim('// ...other getInitialProps code')}
};
`;
}

export function getFullUnderscoreErrorCopyPasteSnippet(isTs: boolean) {
return `
import * as Sentry from '@sentry/nextjs';${
isTs ? '\nimport type { NextPageContext } from "next";' : ''
}
${chalk.dim(
'// Replace "YourCustomErrorComponent" with your custom error component!',
)}
YourCustomErrorComponent.getInitialProps = async (contextData${
isTs ? ': NextPageContext' : ''
}) => {
await Sentry.captureUnderscoreErrorException(contextData);
};
`;
}

0 comments on commit 437a55b

Please sign in to comment.