diff --git a/.gitignore b/.gitignore index 91fff44cb7affb..bdafa5e42fd473 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,6 @@ public/og-images/* yalc.lock /public/doctree.json /public/doctree-dev.json + +# Claude Code local files +.claude/settings.local.json \ No newline at end of file diff --git a/app/[[...path]]/page.tsx b/app/[[...path]]/page.tsx index 89936c5168c0c0..7d3c950ed1d5a0 100644 --- a/app/[[...path]]/page.tsx +++ b/app/[[...path]]/page.tsx @@ -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'; @@ -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, diff --git a/next.config.ts b/next.config.ts index b89fa77ad5236b..bf35ce67e9a10d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -5,71 +5,39 @@ 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/**/*', @@ -77,8 +45,7 @@ const outputFileTracingExcludes = process.env.NEXT_PUBLIC_DEVELOPER_DOCS '**/*.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', @@ -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', diff --git a/src/components/apiPage/index.tsx b/src/components/apiPage/index.tsx index 1fe9ae31d6f231..fbe52d2c8ffd02 100644 --- a/src/components/apiPage/index.tsx +++ b/src/components/apiPage/index.tsx @@ -1,8 +1,8 @@ import {Fragment, ReactElement, useMemo} from 'react'; import {bundleMDX} from 'mdx-bundler'; -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'; diff --git a/src/components/include.tsx b/src/components/include.tsx index 89657516e97e67..04e4a941ae7250 100644 --- a/src/components/include.tsx +++ b/src/components/include.tsx @@ -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'; diff --git a/src/components/platformContent.tsx b/src/components/platformContent.tsx index 1ff6e6de349027..6721fb0d8f534d 100644 --- a/src/components/platformContent.tsx +++ b/src/components/platformContent.tsx @@ -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'; diff --git a/src/getMDXComponent.ts b/src/getMDXComponent.ts new file mode 100644 index 00000000000000..ab5ead661ed29e --- /dev/null +++ b/src/getMDXComponent.ts @@ -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; +} + +/** + * Takes compiled MDX code from bundleMDX and returns a React component. + */ +export function getMDXComponent( + code: string, + globals?: Record +): FunctionComponent { + return getMDXExport(code, globals).default; +} + +/** + * Takes compiled MDX code from bundleMDX and returns all exports. + */ +export function getMDXExport< + ExportedObject = {default: FunctionComponent}, +>(code: string, globals?: Record): 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)); +}