fix(nextjs): Skip dynamic export in App Router routes when cacheComponents enabled#1245
fix(nextjs): Skip dynamic export in App Router routes when cacheComponents enabled#1245cursor[bot] wants to merge 2 commits intomasterfrom
Conversation
…nents enabled - Add hasCacheComponentsEnabled() utility to detect cacheComponents in Next.js config - Update getSentryExampleAppDirApiRoute() to accept includeDynamic parameter - Skip 'export const dynamic = "force-dynamic"' when cacheComponents is enabled - Add comprehensive tests for cacheComponents detection and dynamic export control - Resolves compatibility issue with Next.js 15/16+ Cache Components When cacheComponents is enabled in next.config, Next.js fails at compile time with error: 'Route segment config "dynamic" is not compatible with nextConfig.cacheComponents. Please remove it.' The wizard now detects cacheComponents and omits the dynamic export in generated App Router route files (sentry-example-api), preventing this build failure. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: Kyle a.k.a. TechSquidTV <KyleTryon@users.noreply.github.com>
Co-authored-by: Kyle a.k.a. TechSquidTV <KyleTryon@users.noreply.github.com>
Semver Impact of This PR🟢 Patch (bug fixes) 📋 Changelog PreviewThis is how your changes will appear in the changelog. Bug Fixes
Internal Changes
🤖 This preview updates automatically when you update the PR. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| /experimental\s*:\s*\{\s*cacheComponents\s*:\s*true/.test(configContent) | ||
| ) { | ||
| return true; | ||
| } |
There was a problem hiding this comment.
Regex matches cacheComponents in comments causing false positives
Low Severity
The regex /cacheComponents\s*:\s*true/ matches anywhere in file content, including comments like // cacheComponents: true or strings. Because this check runs before the more accurate magicast AST parsing and short-circuits with return true, a commented-out cacheComponents: true line causes hasCacheComponentsEnabled() to incorrectly report the feature as enabled. This would skip the dynamic export even when cacheComponents isn't actually turned on. The impact is low since Route Handlers are dynamic by default, but the detection logic is logically incorrect.
| // This catches: cacheComponents: true, experimental: { cacheComponents: true } | ||
| if ( | ||
| /cacheComponents\s*:\s*true/.test(configContent) || | ||
| /experimental\s*:\s*\{\s*cacheComponents\s*:\s*true/.test(configContent) |
There was a problem hiding this comment.
Second regex condition is entirely redundant
Low Severity
The second regex /experimental\s*:\s*\{\s*cacheComponents\s*:\s*true/ is entirely redundant because the first regex /cacheComponents\s*:\s*true/ is a strict superset — any string matching the second pattern will always match the first. The || condition means the second branch can never be reached without the first already succeeding.
| return `${sentryImport}export const dynamic = "force-dynamic"; | ||
|
|
||
| class SentryExampleAPIError extends Error { | ||
| return `${sentryImport}${dynamicExport}class SentryExampleAPIError extends Error { |
There was a problem hiding this comment.
Missing blank line between import and class declaration
Low Severity
When includeDynamic is false and logsEnabled is true, the sentryImport string ends with a single newline and dynamicExport is empty, so the generated file has no blank line between the import statement and the class SentryExampleAPIError declaration. Previously, the dynamicExport content (which ended with \n\n) always provided that visual and linter-expected separation. This could trigger lint rules like import/newline-after-import in the generated example file.


Problem
The Sentry Next.js wizard currently generates App Router API routes (e.g.,
app/api/sentry-example-api/route.ts) withexport const dynamic = "force-dynamic". This causes build failures when the target project hascacheComponents: truein their Next.js config (Next.js 15+/16+).Error:
This is because the
dynamicroute segment config is incompatible with Next.js's Cache Components model introduced in Next.js 15/16.Solution
hasCacheComponentsEnabled()utility function to detectcacheComponentsin Next.js config filesgetSentryExampleAppDirApiRoute()template to accept an optionalincludeDynamicparameter (defaults totruefor backward compatibility)cacheComponentsand skip thedynamicexport when it's enabledChanges
New utility function (
src/nextjs/utils.ts):hasCacheComponentsEnabled()- DetectscacheComponents: truein Next.js config files using regex and magicast parsing.js,.mjs,.ts,.mts,.cjs,.cts)Template update (
src/nextjs/templates.ts):getSentryExampleAppDirApiRoute()now acceptsincludeDynamic?: booleanparameterfalse, omits theexport const dynamic = "force-dynamic"lineWizard integration (
src/nextjs/nextjs-wizard.ts):cacheComponentsbefore generating the example API routeincludeDynamic: !cacheComponentsEnabledto the templateTests (
test/nextjs/utils.test.ts,test/nextjs/templates.test.ts):hasCacheComponentsEnabled()covering various config scenariosDocumentation (
CHANGELOG.md):Rationale
Route Handlers in the App Router are already dynamic by default and don't require
export const dynamic = "force-dynamic"for typical use cases like Sentry's example API routes. The export was legacy/defensive, and removing it when incompatible with Cache Components is the correct approach.For older Next.js versions or projects without
cacheComponents, the behavior remains unchanged (dynamic export is still included).Testing
yarn fix)dynamicexport)Related
Closes the issue described in the Slack thread regarding Next.js dynamic route compatibility with Cache Components.
Slack Thread