Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,6 @@ public/og-images/*
yalc.lock
/public/doctree.json
/public/doctree-dev.json

# Claude Code local files
.claude/settings.local.json
2 changes: 1 addition & 1 deletion app/[[...path]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {Fragment, useMemo} from 'react';
import * as Sentry from '@sentry/nextjs';
import {getMDXComponent} from 'mdx-bundler/client';
import {Metadata} from 'next';
import {notFound} from 'next/navigation';

Expand All @@ -20,6 +19,7 @@ import {
getPreviousNode,
nodeForPath,
} from 'sentry-docs/docTree';
import {getMDXComponent} from 'sentry-docs/getMDXComponent';
import {isDeveloperDocs} from 'sentry-docs/isDeveloperDocs';
import {
getDevDocsFrontMatter,
Expand Down
104 changes: 33 additions & 71 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,80 +5,47 @@ import {REMOTE_IMAGE_PATTERNS} from './src/config/images';
import {redirects} from './redirects.js';

// Exclude build-time-only dependencies from serverless function bundles to stay under
// Vercel's 250MB limit. These packages (esbuild, mdx-bundler, sharp, etc.) are only
// needed during the build process to compile MDX and optimize assets. The compiled
// output is used at runtime, so bundling these ~150-200MB of dependencies would bloat
// functions unnecessarily and cause deployment failures.
//
// Note: mdx-bundler/client (getMDXComponent) is a tiny runtime module needed by
// app/[[...path]]/page.tsx, so we exclude only the build parts (dist/!(client)*).
// Vercel's 250MB limit. These packages are only needed during build to compile MDX and
// optimize assets. We use a local getMDXComponent (src/getMDXComponent.ts) instead of
// mdx-bundler/client to avoid CJS/ESM compatibility issues at runtime.
const sharedExcludes = [
'**/*.map',
'./.git/**/*',
'./apps/**/*',
'./.next/cache/mdx-bundler/**/*',
'./.next/cache/md-exports/**/*',
// Heavy build dependencies
'node_modules/@esbuild/**/*',
'node_modules/esbuild/**/*',
'node_modules/@aws-sdk/**/*',
'node_modules/@google-cloud/**/*',
'node_modules/prettier/**/*',
'node_modules/@prettier/**/*',
'node_modules/sharp/**/*',
'node_modules/mermaid/**/*',
// MDX processing dependencies (local getMDXComponent replaces mdx-bundler/client)
'node_modules/mdx-bundler/**/*',
'node_modules/rehype-preset-minify/**/*',
'node_modules/rehype-prism-plus/**/*',
'node_modules/rehype-prism-diff/**/*',
'node_modules/remark-gfm/**/*',
'node_modules/remark-mdx-images/**/*',
'node_modules/unified/**/*',
'node_modules/rollup/**/*',
];

const outputFileTracingExcludes = process.env.NEXT_PUBLIC_DEVELOPER_DOCS
? {
'/**/*': [
'**/*.map',
'./.git/**/*',
'./apps/**/*',
'./.next/cache/mdx-bundler/**/*',
'./.next/cache/md-exports/**/*',
'docs/**/*',
// Exclude heavy build dependencies
'node_modules/@esbuild/**/*',
'node_modules/esbuild/**/*',
'node_modules/@aws-sdk/**/*',
'node_modules/@google-cloud/**/*',
'node_modules/prettier/**/*',
'node_modules/@prettier/**/*',
'node_modules/sharp/**/*',
'node_modules/mermaid/**/*',
// Exclude MDX processing dependencies (but keep mdx-bundler/client for runtime)
'node_modules/mdx-bundler/dist/!(client*)',
'node_modules/mdx-bundler/node_modules/**/*',
'node_modules/rehype-preset-minify/**/*',
'node_modules/rehype-prism-plus/**/*',
'node_modules/rehype-prism-diff/**/*',
'node_modules/remark-gfm/**/*',
'node_modules/remark-mdx-images/**/*',
'node_modules/unified/**/*',
'node_modules/rollup/**/*',
],
}
? {'/**/*': [...sharedExcludes, 'docs/**/*']}
: {
'/**/*': [
'**/*.map',
'./.git/**/*',
'./.next/cache/mdx-bundler/**/*',
'./.next/cache/md-exports/**/*',
'./apps/**/*',
'develop-docs/**/*',
// Exclude heavy build dependencies
'node_modules/@esbuild/**/*',
'node_modules/esbuild/**/*',
'node_modules/@aws-sdk/**/*',
'node_modules/@google-cloud/**/*',
'node_modules/prettier/**/*',
'node_modules/@prettier/**/*',
'node_modules/sharp/**/*',
'node_modules/mermaid/**/*',
// Exclude MDX processing dependencies (but keep mdx-bundler/client for runtime)
'node_modules/mdx-bundler/dist/!(client*)',
'node_modules/mdx-bundler/node_modules/**/*',
'node_modules/rehype-preset-minify/**/*',
'node_modules/rehype-prism-plus/**/*',
'node_modules/rehype-prism-diff/**/*',
'node_modules/remark-gfm/**/*',
'node_modules/remark-mdx-images/**/*',
'node_modules/unified/**/*',
'node_modules/rollup/**/*',
],
'/**/*': [...sharedExcludes, 'develop-docs/**/*'],
'/platform-redirect': [
'**/*.gif',
'public/mdx-images/**/*',
'public/og-images/**/*',
'**/*.pdf',
],
'\\[\\[\\.\\.\\.path\\]\\]': [
// Exclude docs to save ~156MB, but allow specific files via outputFileTracingIncludes
// for pages that may be accessed at runtime (error pages, cold starts, etc.)
// Exclude docs to save ~156MB, allow specific files via outputFileTracingIncludes
'docs/**/*',
'node_modules/prettier/plugins',
'node_modules/rollup/dist',
Expand Down Expand Up @@ -138,12 +105,7 @@ const nextConfig = {
'@esbuild/linux-arm64',
'@esbuild/linux-x64',
'@esbuild/win32-x64',
// Note: mdx-bundler is intentionally NOT in serverExternalPackages.
// The package is ESM-only ("type": "module") and cannot be require()'d at runtime.
// Keeping it out allows webpack to bundle mdx-bundler/client properly while
// outputFileTracingExcludes still prevents the heavy build-time parts from
// being included in the serverless function bundle.
// Fixes: DOCS-A0W
// mdx-bundler fully excluded via outputFileTracingExcludes
'sharp',
'@aws-sdk/client-s3',
'@google-cloud/storage',
Expand Down
2 changes: 1 addition & 1 deletion src/components/apiPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Fragment, ReactElement, useMemo} from 'react';
import {bundleMDX} from 'mdx-bundler';
Copy link

Choose a reason for hiding this comment

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

Bug: The ApiPage component calls bundleMDX at runtime, but mdx-bundler is excluded from the serverless bundle, which will cause a module resolution error on API pages.
Severity: CRITICAL

Suggested Fix

Since mdx-bundler is required at runtime for API pages, it should not be excluded from the serverless bundle. Remove the 'node_modules/mdx-bundler/**/*' exclusion from next.config.ts. Alternatively, pre-process the API descriptions at build time so that bundleMDX is not needed at runtime.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/components/apiPage/index.tsx#L2

Potential issue: API documentation pages are rendered dynamically at runtime. The
`ApiPage` component uses the `bundleMDX` function from the `mdx-bundler` package to
parse markdown in API parameter descriptions. However, the `next.config.ts` file has
been updated to exclude the `mdx-bundler` package from the serverless function bundle.
When a user navigates to an API page, the server will attempt to call `bundleMDX`, fail
to find the module, and throw a runtime error, causing the page request to fail.

import {getMDXComponent} from 'mdx-bundler/client';

import {type API} from 'sentry-docs/build/resolveOpenAPI';
import {getMDXComponent} from 'sentry-docs/getMDXComponent';
import {mdxComponents} from 'sentry-docs/mdxComponents';
import remarkCodeTabs from 'sentry-docs/remark-code-tabs';
import remarkCodeTitles from 'sentry-docs/remark-code-title';
Expand Down
2 changes: 1 addition & 1 deletion src/components/include.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useMemo} from 'react';
import {getMDXComponent} from 'mdx-bundler/client';

import {getMDXComponent} from 'sentry-docs/getMDXComponent';
import {getFileBySlugWithCache} from 'sentry-docs/mdx';
import {mdxComponents} from 'sentry-docs/mdxComponents';

Expand Down
2 changes: 1 addition & 1 deletion src/components/platformContent.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import fs from 'fs';

import {cache, useMemo} from 'react';
import {getMDXComponent} from 'mdx-bundler/client';

import {getCurrentGuide, getDocsRootNode, getPlatform} from 'sentry-docs/docTree';
import {getMDXComponent} from 'sentry-docs/getMDXComponent';
import {getFileBySlugWithCache} from 'sentry-docs/mdx';
import {mdxComponents} from 'sentry-docs/mdxComponents';
import {serverContext} from 'sentry-docs/serverContext';
Expand Down
41 changes: 41 additions & 0 deletions src/getMDXComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Local implementation of getMDXComponent replacing mdx-bundler/client.
*
* Eliminates the runtime dependency on mdx-bundler/client which has CJS/ESM
* compatibility issues in Vercel serverless functions. Since getMDXComponent
* only needs React at runtime, we can safely inline this implementation.
*/
import type {ComponentType, FunctionComponent} from 'react';
// Namespace imports required - MDX runtime expects React, ReactDOM, jsx_runtime in scope
// eslint-disable-next-line no-restricted-imports
import * as React from 'react';
import * as jsxRuntime from 'react/jsx-runtime';
// eslint-disable-next-line no-restricted-imports
import * as ReactDOM from 'react-dom';

export interface MDXContentProps {
[key: string]: unknown;
components?: Record<string, ComponentType>;
}

/**
* Takes compiled MDX code from bundleMDX and returns a React component.
*/
export function getMDXComponent(
code: string,
globals?: Record<string, unknown>
): FunctionComponent<MDXContentProps> {
return getMDXExport(code, globals).default;
}

/**
* Takes compiled MDX code from bundleMDX and returns all exports.
*/
export function getMDXExport<
ExportedObject = {default: FunctionComponent<MDXContentProps>},
>(code: string, globals?: Record<string, unknown>): ExportedObject {
const scope = {React, ReactDOM, _jsx_runtime: jsxRuntime, ...globals};
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
const fn = new Function(...Object.keys(scope), code);
return fn(...Object.values(scope));
}
Loading