@@ -41,14 +41,16 @@ import {
4141 getNextjsConfigEsmCopyPasteSnippet ,
4242 getNextjsConfigMjsTemplate ,
4343 getRootLayout ,
44- getSentryConfigContents ,
44+ getSentryServersideConfigContents ,
45+ getInstrumentationClientFileContents ,
4546 getSentryDefaultGlobalErrorPage ,
4647 getSentryDefaultUnderscoreErrorPage ,
4748 getSentryExampleAppDirApiRoute ,
4849 getSentryExamplePageContents ,
4950 getSentryExamplePagesDirApiRoute ,
5051 getSimpleUnderscoreErrorCopyPasteSnippet ,
5152 getWithSentryConfigOptionsTemplate ,
53+ getInstrumentationClientHookCopyPasteSnippet ,
5254} from './templates' ;
5355import { getNextJsVersionBucket } from './utils' ;
5456
@@ -99,7 +101,7 @@ export async function runNextjsWizardWithTelemetry(
99101
100102 const { packageManager : packageManagerFromInstallStep } =
101103 await installPackage ( {
102- packageName : '@sentry/nextjs@^9 ' ,
104+ packageName : '@sentry/nextjs@latest ' ,
103105 packageNameDisplayLabel : '@sentry/nextjs' ,
104106 alreadyInstalled : ! ! packageJson ?. dependencies ?. [ '@sentry/nextjs' ] ,
105107 forceInstall,
@@ -311,8 +313,8 @@ export async function runNextjsWizardWithTelemetry(
311313 if ( isLikelyUsingTurbopack || isLikelyUsingTurbopack === null ) {
312314 await abortIfCancelled (
313315 clack . select ( {
314- message : `Warning: The Sentry SDK doesn't yet fully support Turbopack in dev mode. The SDK will not be loaded in the browser, and serverside instrumentation will be inaccurate or incomplete. Production builds will still fully work . ${ chalk . bold (
315- `To continue this setup, if you are using Turbopack, temporarily remove \`--turbo\` or \`--turbopack\` from your dev command until you have verified the SDK is working as expected.` ,
316+ message : `Warning: The Sentry SDK is only compatible with Turbopack on Next.js version 15.3.0 ( or 15.3.0-canary.8) or later . ${ chalk . bold (
317+ `If you are using Turbopack with an older Next.js version , temporarily remove \`--turbo\` or \`--turbopack\` from your dev command until you have verified the SDK is working as expected. Note that the SDK will continue to work for non-Turbopack production builds .` ,
316318 ) } `,
317319 options : [
318320 {
@@ -392,7 +394,7 @@ async function createOrMergeNextJsFiles(
392394
393395 const typeScriptDetected = isUsingTypeScript ( ) ;
394396
395- const configVariants = [ 'server' , 'client' , ' edge'] as const ;
397+ const configVariants = [ 'server' , 'edge' ] as const ;
396398
397399 for ( const configVariant of configVariants ) {
398400 await traceStep ( `create-sentry-${ configVariant } -config` , async ( ) => {
@@ -444,7 +446,7 @@ async function createOrMergeNextJsFiles(
444446 if ( shouldWriteFile ) {
445447 await fs . promises . writeFile (
446448 path . join ( process . cwd ( ) , typeScriptDetected ? tsConfig : jsConfig ) ,
447- getSentryConfigContents (
449+ getSentryServersideConfigContents (
448450 selectedProject . keys [ 0 ] . dsn . public ,
449451 configVariant ,
450452 selectedFeatures ,
@@ -532,6 +534,7 @@ async function createOrMergeNextJsFiles(
532534 getInstrumentationHookCopyPasteSnippet (
533535 newInstrumentationHookLocation ,
534536 ) ,
537+ "create the file if it doesn't already exist" ,
535538 ) ;
536539 }
537540 } else {
@@ -546,6 +549,102 @@ async function createOrMergeNextJsFiles(
546549 }
547550 } ) ;
548551
552+ await traceStep ( 'setup-instrumentation-client-hook' , async ( ) => {
553+ const hasRootAppDirectory = hasDirectoryPathFromRoot ( 'app' ) ;
554+ const hasRootPagesDirectory = hasDirectoryPathFromRoot ( 'pages' ) ;
555+ const hasSrcDirectory = hasDirectoryPathFromRoot ( 'src' ) ;
556+
557+ let instrumentationClientHookLocation : 'src' | 'root' | 'does-not-exist' ;
558+
559+ const instrumentationClientTsExists = fs . existsSync (
560+ path . join ( process . cwd ( ) , 'instrumentation-client.ts' ) ,
561+ ) ;
562+ const instrumentationClientJsExists = fs . existsSync (
563+ path . join ( process . cwd ( ) , 'instrumentation-client.js' ) ,
564+ ) ;
565+ const srcInstrumentationClientTsExists = fs . existsSync (
566+ path . join ( process . cwd ( ) , 'src' , 'instrumentation-client.ts' ) ,
567+ ) ;
568+ const srcInstrumentationClientJsExists = fs . existsSync (
569+ path . join ( process . cwd ( ) , 'src' , 'instrumentation-client.js' ) ,
570+ ) ;
571+
572+ // https://nextjs.org/docs/app/building-your-application/configuring/src-directory
573+ // https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation
574+ // The logic for where Next.js picks up the instrumentation file is as follows:
575+ // - If there is either an `app` folder or a `pages` folder in the root directory of your Next.js app, Next.js looks
576+ // for an `instrumentation.ts` file in the root of the Next.js app.
577+ // - Otherwise, if there is neither an `app` folder or a `pages` folder in the rood directory of your Next.js app,
578+ // AND if there is an `src` folder, Next.js will look for the `instrumentation.ts` file in the `src` folder.
579+ if ( hasRootPagesDirectory || hasRootAppDirectory ) {
580+ if ( instrumentationClientJsExists || instrumentationClientTsExists ) {
581+ instrumentationClientHookLocation = 'root' ;
582+ } else {
583+ instrumentationClientHookLocation = 'does-not-exist' ;
584+ }
585+ } else {
586+ if (
587+ srcInstrumentationClientTsExists ||
588+ srcInstrumentationClientJsExists
589+ ) {
590+ instrumentationClientHookLocation = 'src' ;
591+ } else {
592+ instrumentationClientHookLocation = 'does-not-exist' ;
593+ }
594+ }
595+
596+ const newInstrumentationClientFileName = `instrumentation-client.${
597+ typeScriptDetected ? 'ts' : 'js'
598+ } `;
599+
600+ if ( instrumentationClientHookLocation === 'does-not-exist' ) {
601+ let newInstrumentationClientHookLocation : 'root' | 'src' ;
602+ if ( hasRootPagesDirectory || hasRootAppDirectory ) {
603+ newInstrumentationClientHookLocation = 'root' ;
604+ } else if ( hasSrcDirectory ) {
605+ newInstrumentationClientHookLocation = 'src' ;
606+ } else {
607+ newInstrumentationClientHookLocation = 'root' ;
608+ }
609+
610+ const newInstrumentationClientHookPath =
611+ newInstrumentationClientHookLocation === 'root'
612+ ? path . join ( process . cwd ( ) , newInstrumentationClientFileName )
613+ : path . join ( process . cwd ( ) , 'src' , newInstrumentationClientFileName ) ;
614+
615+ const successfullyCreated = await createNewConfigFile (
616+ newInstrumentationClientHookPath ,
617+ getInstrumentationClientFileContents (
618+ selectedProject . keys [ 0 ] . dsn . public ,
619+ selectedFeatures ,
620+ ) ,
621+ ) ;
622+
623+ if ( ! successfullyCreated ) {
624+ await showCopyPasteInstructions (
625+ newInstrumentationClientFileName ,
626+ getInstrumentationClientHookCopyPasteSnippet (
627+ selectedProject . keys [ 0 ] . dsn . public ,
628+ selectedFeatures ,
629+ ) ,
630+ "create the file if it doesn't already exist" ,
631+ ) ;
632+ }
633+ } else {
634+ await showCopyPasteInstructions (
635+ srcInstrumentationClientTsExists || instrumentationClientTsExists
636+ ? 'instrumentation-client.ts'
637+ : srcInstrumentationClientJsExists || instrumentationClientJsExists
638+ ? 'instrumentation-client.js'
639+ : newInstrumentationClientFileName ,
640+ getInstrumentationClientHookCopyPasteSnippet (
641+ selectedProject . keys [ 0 ] . dsn . public ,
642+ selectedFeatures ,
643+ ) ,
644+ ) ;
645+ }
646+ } ) ;
647+
549648 await traceStep ( 'setup-next-config' , async ( ) => {
550649 const withSentryConfigOptionsTemplate = getWithSentryConfigOptionsTemplate ( {
551650 orgSlug : selectedProject . organization . slug ,
0 commit comments