Skip to content

fix(nextjs): Skip dynamic export in App Router routes when cacheComponents enabled#1245

Open
cursor[bot] wants to merge 2 commits intomasterfrom
cursor/next-js-dynamic-route-compatibility-a42f
Open

fix(nextjs): Skip dynamic export in App Router routes when cacheComponents enabled#1245
cursor[bot] wants to merge 2 commits intomasterfrom
cursor/next-js-dynamic-route-compatibility-a42f

Conversation

@cursor
Copy link
Contributor

@cursor cursor bot commented Mar 24, 2026

Problem

The Sentry Next.js wizard currently generates App Router API routes (e.g., app/api/sentry-example-api/route.ts) with export const dynamic = "force-dynamic". This causes build failures when the target project has cacheComponents: true in their Next.js config (Next.js 15+/16+).

Error:

Route segment config "dynamic" is not compatible with nextConfig.cacheComponents. Please remove it.

This is because the dynamic route segment config is incompatible with Next.js's Cache Components model introduced in Next.js 15/16.

Solution

  • Added hasCacheComponentsEnabled() utility function to detect cacheComponents in Next.js config files
  • Updated getSentryExampleAppDirApiRoute() template to accept an optional includeDynamic parameter (defaults to true for backward compatibility)
  • Modified the wizard to detect cacheComponents and skip the dynamic export when it's enabled
  • Added comprehensive tests for both the detection logic and template generation

Changes

  1. New utility function (src/nextjs/utils.ts):

    • hasCacheComponentsEnabled() - Detects cacheComponents: true in Next.js config files using regex and magicast parsing
    • Supports all Next.js config file variants (.js, .mjs, .ts, .mts, .cjs, .cts)
  2. Template update (src/nextjs/templates.ts):

    • getSentryExampleAppDirApiRoute() now accepts includeDynamic?: boolean parameter
    • When false, omits the export const dynamic = "force-dynamic" line
  3. Wizard integration (src/nextjs/nextjs-wizard.ts):

    • Detects cacheComponents before generating the example API route
    • Passes includeDynamic: !cacheComponentsEnabled to the template
  4. Tests (test/nextjs/utils.test.ts, test/nextjs/templates.test.ts):

    • Added tests for hasCacheComponentsEnabled() covering various config scenarios
    • Added tests for template generation with and without the dynamic export
    • All existing tests continue to pass
  5. Documentation (CHANGELOG.md):

    • Added entry under "Unreleased" section documenting this bug fix

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

  • ✅ All unit tests pass (206 tests)
  • ✅ Linting and formatting pass (yarn fix)
  • ✅ Backward compatibility maintained (defaults to including dynamic export)
  • ✅ New tests verify cacheComponents detection and conditional template generation

Related

Closes the issue described in the Slack thread regarding Next.js dynamic route compatibility with Cache Components.

Slack Thread

Open in Web Open in Cursor 

cursoragent and others added 2 commits March 24, 2026 17:13
…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>
@github-actions
Copy link

github-actions bot commented Mar 24, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


Bug Fixes

  • (nextjs) Skip dynamic export in App Router routes when cacheComponents enabled by cursor[bot] in #1245

Internal Changes

  • (agents) Convert commands to dotagents skills by chargome in #1236

🤖 This preview updates automatically when you update the PR.

@KyleTryon KyleTryon marked this pull request as ready for review March 24, 2026 17:28
@KyleTryon KyleTryon requested a review from andreiborza March 24, 2026 17:30
Copy link
Contributor Author

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

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;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

// This catches: cacheComponents: true, experimental: { cacheComponents: true }
if (
/cacheComponents\s*:\s*true/.test(configContent) ||
/experimental\s*:\s*\{\s*cacheComponents\s*:\s*true/.test(configContent)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

return `${sentryImport}export const dynamic = "force-dynamic";

class SentryExampleAPIError extends Error {
return `${sentryImport}${dynamicExport}class SentryExampleAPIError extends Error {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant