From 71e5d9f7763c0246b4cd028a20b2a68fff943615 Mon Sep 17 00:00:00 2001 From: Andreas Heissenberger Date: Fri, 14 Nov 2025 10:14:42 +0100 Subject: [PATCH 1/7] feat: add source map support for production stack trace rewriting --- packages/react-server/lib/start/action.mjs | 4 + .../react-server/lib/start/ssr-handler.mjs | 21 ++ packages/react-server/package.json | 1 + packages/react-server/server/symbols.mjs | 1 + pnpm-lock.yaml | 222 ++++++++++++++++-- 5 files changed, 223 insertions(+), 26 deletions(-) diff --git a/packages/react-server/lib/start/action.mjs b/packages/react-server/lib/start/action.mjs index e7251006..234009ad 100644 --- a/packages/react-server/lib/start/action.mjs +++ b/packages/react-server/lib/start/action.mjs @@ -14,6 +14,7 @@ import { CONFIG_ROOT, LOGGER_CONTEXT, SERVER_CONTEXT, + SOURCEMAP_ENABLED, } from "../../server/symbols.mjs"; import { formatDuration } from "../utils/format.mjs"; import getServerAddresses from "../utils/server-address.mjs"; @@ -45,6 +46,9 @@ async function worker(root, options, config) { await runtime_init$(async () => { runtime$(CONFIG_CONTEXT, config); + // Check if sourcemaps are available by looking for .map files in build output + // This will be set to true if sourcemaps were generated during build + runtime$(SOURCEMAP_ENABLED, false); const logger = await createLogger(configRoot); const server = await createServer(root, options); const { port, listenerHost } = getServerConfig(configRoot, options); diff --git a/packages/react-server/lib/start/ssr-handler.mjs b/packages/react-server/lib/start/ssr-handler.mjs index 6dcf8a8d..ca982ad3 100644 --- a/packages/react-server/lib/start/ssr-handler.mjs +++ b/packages/react-server/lib/start/ssr-handler.mjs @@ -1,3 +1,4 @@ +import { access, constants as fsConstants } from "node:fs/promises"; import { createRequire, register } from "node:module"; import { join, relative } from "node:path"; import { pathToFileURL } from "node:url"; @@ -38,6 +39,7 @@ import { RENDER_CONTEXT, RENDER_STREAM, SERVER_CONTEXT, + SOURCEMAP_ENABLED, STYLES_CONTEXT, } from "../../server/symbols.mjs"; import { ContextManager } from "../async-local-storage.mjs"; @@ -109,6 +111,25 @@ export default async function ssrHandler(root, options = {}) { const moduleCacheStorage = new ContextManager(); await module_loader_init$(moduleLoader, moduleCacheStorage); + // Check if sourcemaps are available by checking if entryModule has a .map file + let sourcemapEnabled = false; + try { + await access(`${entryModule}.map`, fsConstants.R_OK); + sourcemapEnabled = true; + // Dynamically import and install source-map-support only when source maps are available + const { default: sourceMapSupport } = await import("source-map-support"); + sourceMapSupport.install({ + environment: "node", + hookRequire: false, + handleUncaughtExceptions: false, + }); + console.log("[SourceMap] Source map support enabled"); + } catch { + // Sourcemaps not available + console.log("[SourceMap] No source maps found"); + } + runtime$(SOURCEMAP_ENABLED, sourcemapEnabled); + const importMap = configRoot.importMap || configRoot.resolve?.shared ? { diff --git a/packages/react-server/package.json b/packages/react-server/package.json index b56129da..bee027b9 100644 --- a/packages/react-server/package.json +++ b/packages/react-server/package.json @@ -163,6 +163,7 @@ "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", "source-map": "^0.7.4", + "source-map-support": "^0.5.21", "strip-ansi": "^7.1.0", "style-to-js": "^1.1.12", "unstorage": "^1.16.0", diff --git a/packages/react-server/server/symbols.mjs b/packages/react-server/server/symbols.mjs index 882ba011..9ba129f7 100644 --- a/packages/react-server/server/symbols.mjs +++ b/packages/react-server/server/symbols.mjs @@ -26,6 +26,7 @@ export const SSR_CONTROLLER = Symbol.for("SSR_CONTROLLER"); export const ROUTE_MATCH = Symbol.for("ROUTE_MATCH"); export const STYLES_CONTEXT = Symbol.for("STYLES_CONTEXT"); export const BUILD_OPTIONS = Symbol.for("BUILD_OPTIONS"); +export const SOURCEMAP_ENABLED = Symbol.for("SOURCEMAP_ENABLED"); export const ACTION_CONTEXT = Symbol.for("ACTION_CONTEXT"); export const RELOAD = Symbol.for("RELOAD"); export const RENDER = Symbol.for("RENDER"); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 173b7cc3..662395c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,7 +150,7 @@ importers: version: 4.0.0 vite-plugin-svgr: specifier: ^4.2.0 - version: 4.2.0(rollup@4.40.1)(typescript@5.9.3) + version: 4.2.0(rollup@4.40.1)(typescript@5.9.3)(vite@6.4.1(@types/node@24.9.2)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.6))(terser@5.37.0)(yaml@2.5.0)) devDependencies: '@inlang/paraglide-js': specifier: 1.11.8 @@ -238,6 +238,9 @@ importers: '@types/react-dom': specifier: ^19 version: 19.2.2(@types/react@19.2.2) + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 examples/file-upload: dependencies: @@ -526,7 +529,7 @@ importers: version: 8.5.3 tailwindcss: specifier: ^3.4.3 - version: 3.4.4(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@24.9.2)(typescript@5.9.3)) + version: 3.4.4(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@20.17.11)(typescript@5.9.3)) examples/pokemon: dependencies: @@ -779,7 +782,7 @@ importers: version: 8.5.3 tailwindcss: specifier: ^3.4.3 - version: 3.4.4(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@24.9.2)(typescript@5.9.3)) + version: 3.4.4(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@20.17.11)(typescript@5.9.3)) examples/tanstack-router: dependencies: @@ -795,7 +798,7 @@ importers: version: 1.43.3(@tanstack/react-router@1.43.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(csstype@3.1.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tanstack/router-plugin': specifier: ^1.39.13 - version: 1.43.1 + version: 1.43.1(vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.37.0)(yaml@2.5.0)) '@types/react': specifier: ^18.3.2 version: 18.3.5 @@ -810,7 +813,7 @@ importers: version: 8.5.3 tailwindcss: specifier: ^3.4.3 - version: 3.4.4(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@24.9.2)(typescript@5.9.3)) + version: 3.4.4(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@20.17.11)(typescript@5.9.3)) examples/todo: dependencies: @@ -841,7 +844,7 @@ importers: version: 8.5.3 tailwindcss: specifier: ^3.4.3 - version: 3.4.4(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@24.9.2)(typescript@5.9.3)) + version: 3.4.4(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@20.17.11)(typescript@5.9.3)) packages/create-react-server: dependencies: @@ -937,7 +940,7 @@ importers: version: 18.3.0 '@vitejs/plugin-react-oxc': specifier: ^0.1.1 - version: 0.1.1 + version: 0.1.1(vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(terser@5.37.0)(yaml@2.5.0)) acorn-loose: specifier: ^8.4.0 version: 8.4.0 @@ -1049,6 +1052,9 @@ importers: source-map: specifier: ^0.7.4 version: 0.7.4 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 strip-ansi: specifier: ^7.1.0 version: 7.1.0 @@ -1060,7 +1066,7 @@ importers: version: 1.16.0(idb-keyval@6.2.2) vite-plugin-inspect: specifier: ^11.3.3 - version: 11.3.3 + version: 11.3.3(vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(terser@5.37.0)(yaml@2.5.0)) web-streams-polyfill: specifier: ^4.2.0 version: 4.2.0 @@ -2621,27 +2627,33 @@ packages: '@inlang/detect-json-formatting@1.0.0': resolution: {integrity: sha512-o0jeI8U4TgNlsPwI0y92jld8/18Loh2KEgHCYCJ42rCOdxFrA8R60cydlEd2/6jkdHFn5DxKj8rOyiKv3z9uOw==} + deprecated: no longer used '@inlang/json-types@1.1.0': resolution: {integrity: sha512-n6vS6AqETsCFbV4TdBvR/EH57waVXzKsMqeUQ+eH2Q6NUATfKhfLabgNms2A+QV3aedH/hLtb1pRmjl2ykBVZg==} + deprecated: no longer used peerDependencies: '@sinclair/typebox': ^0.31.0 '@inlang/language-tag@1.5.1': resolution: {integrity: sha512-+NlYDxDvN5h/TKUmkuQv+Ct1flxaVRousCbek7oFEk3/afZPVLNTJhm+cX2xiOg3tmi2KKrBLfy/V9oUDHj6GQ==} + deprecated: use the inlang sdk directly https://www.npmjs.com/package/@inlang/sdk '@inlang/message-lint-rule@1.4.7': resolution: {integrity: sha512-FCiFe/H25fqhsIb/YTb0K7eDJqEYzdr6ectF0xG4zARiS7nXz0FHxk2niJrIO8kFkB4mx6tszsgQ0xqD5cHQag==} + deprecated: use the inlang sdk directly https://www.npmjs.com/package/@inlang/sdk peerDependencies: '@sinclair/typebox': ^0.31.17 '@inlang/message@2.1.0': resolution: {integrity: sha512-Gr3wiErI7fW4iW11xgZzsJEUTjlZuz02fB/EO+ENTBlSHGyI1kzbCCeNqLr1mnGdQYiOxfuZxY0S4G5C6Pju3Q==} + deprecated: use the inlang sdk directly https://www.npmjs.com/package/@inlang/sdk peerDependencies: '@sinclair/typebox': ^0.31.17 '@inlang/module@1.2.14': resolution: {integrity: sha512-Z7rRa6x3RkzjdvNA7x+KskNGdSBEO46X9c7bTl6eZmLXy0J9yGDn6s4jpYqQzyKRG8g5mEqWcRqcVqdNwzj5Gg==} + deprecated: use the inlang sdk directly https://www.npmjs.com/package/@inlang/sdk peerDependencies: '@sinclair/typebox': ^0.31.17 @@ -2651,6 +2663,7 @@ packages: '@inlang/paraglide-unplugin@1.9.5': resolution: {integrity: sha512-5KklLBvl/y+R4SccWH74USTGQNFW5IwEyMLQ3WIHX9cHX2pnnA5wGqQxYg3EcgCyErHLc3+sm7EMNB5Z0dSeTg==} + deprecated: use the paraglide-js package directly which exports all bundler plugins in v2 and above https://www.npmjs.com/package/@inlang/paraglide-js '@inlang/paraglide-vite@1.3.5': resolution: {integrity: sha512-yLa+gxA8el6RXXneeiqTnV9Od4Yh389lA+wSfiS+jDXY5vV/2j7Lpk2yuATLmxwI9i2nMP6c6yu8L0X77PA9dg==} @@ -2661,6 +2674,7 @@ packages: '@inlang/plugin@2.4.14': resolution: {integrity: sha512-HFI1t1tKs6jXqwKVl59vvt7kvMgg2Po7xA3IFijfJTZCt0tTI8txqeXCUV9jhUop29Hqj6a5zQd32BYv33Dulw==} + deprecated: use the inlang sdk directly https://www.npmjs.com/package/@inlang/sdk peerDependencies: '@sinclair/typebox': ^0.31.17 @@ -2671,12 +2685,14 @@ packages: '@inlang/recommend-ninja@0.1.1': resolution: {integrity: sha512-dthW8SA6LHUhPFXwKxYy92PG4dg4KeIS0jbgpplXxgoQAeouP6DHEa87kva2DXbk3kUbNz+/MFPjyaygBfamog==} + deprecated: ninja got deprecated in favor of lix validation rules https://github.com/opral/lix-sdk/issues/239 '@inlang/recommend-sherlock@0.1.1': resolution: {integrity: sha512-8qZ8FJ/QqVh6YqKmHo3SxI4ENM0O80TCzETm+hxeQ2JzPKPFYucFINpLvUygiLFp/hJwhoI5TjRz6jNI2QdfMQ==} '@inlang/result@1.1.0': resolution: {integrity: sha512-zLGroi9EUiHuOjUOaglUVTFO7EWdo2OARMJLBO1Q5Ga/xJmSQb6XS1lhqEXBFAjgFarfEMX5YEJWWALogYV3wA==} + deprecated: result is no longer used '@inlang/sdk@0.36.3': resolution: {integrity: sha512-wjsavc44H24v74tdEQ13FqZZcr43T106oEfHJnBLzEP55Zz2JJWABLund+DEdosZx+9E8mJBEW5JlVnlBwP3Zw==} @@ -2692,6 +2708,7 @@ packages: '@inlang/translatable@1.3.1': resolution: {integrity: sha512-VAtle21vRpIrB+axtHFrFB0d1HtDaaNj+lV77eZQTJyOWbTFYTVIQJ8WAbyw9eu4F6h6QC2FutLyxjMomxfpcQ==} + deprecated: no longer used '@inquirer/checkbox@4.1.1': resolution: {integrity: sha512-os5kFd/52gZTl/W6xqMfhaKVJHQM8V/U1P8jcSaQJ/C4Qhdrf2jEXdA/HaxfQs9iiUA/0yzYhk5d3oRHTxGDDQ==} @@ -2922,9 +2939,11 @@ packages: '@lix-js/client@2.2.1': resolution: {integrity: sha512-6DTJdRN2L2a1A8OxW1Wqh3ZOORqq8+YlCALMF5UMoxhfHE4Fcq9FZztMkAV+KwhrDSsp0USWvD9myG0XX+v6QQ==} + deprecated: use the lix sdk instead https://www.npmjs.com/package/@lix-js/sdk '@lix-js/fs@2.2.0': resolution: {integrity: sha512-B9X3FjD8WmdG7tbA44JuniSO0KdKBWnjfxl8zpgrDCkavrp/GP7U0xxBkc0WgeeoHjQ/pkqq9VqtWB2kS9jIUg==} + deprecated: use the lix sdk instead https://www.npmjs.com/package/@lix-js/sdk '@ljharb/through@2.3.13': resolution: {integrity: sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==} @@ -4611,12 +4630,12 @@ packages: '@tailwindcss/vite@4.0.9': resolution: {integrity: sha512-BIKJO+hwdIsN7V6I7SziMZIVHWWMsV/uCQKYEbeiGRDRld+TkqyRRl9+dQ0MCXbhcVr+D9T/qX2E84kT7V281g==} peerDependencies: - vite: npm:rolldown-vite@7.0.12 + vite: ^5.2.0 || ^6 '@tailwindcss/vite@4.1.16': resolution: {integrity: sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg==} peerDependencies: - vite: npm:rolldown-vite@7.0.12 + vite: ^5.2.0 || ^6 || ^7 '@tanstack/history@1.41.0': resolution: {integrity: sha512-euTyZoHidW1+NeAW9V7SSPNjD6c54TBqKBO8HypA880HWlTXLW6V8rcBnfi1LY1W706dGCvDmZDTg6fsl/jJUw==} @@ -4667,7 +4686,7 @@ packages: engines: {node: '>=12'} peerDependencies: '@rsbuild/core': '>=0.7.9' - vite: npm:rolldown-vite@7.0.12 + vite: '>=5.0.13' peerDependenciesMeta: '@rsbuild/core': optional: true @@ -5266,7 +5285,7 @@ packages: resolution: {integrity: sha512-1pGZcXfB5JGPuEwHxwScknfO6Lmz9agEmCuG6diruRhBjMgdro5tjJbz3dKlqxibTtpsiX01aaLHhfpc0l9/yg==} engines: {node: '>=20.0.0'} peerDependencies: - vite: npm:rolldown-vite@7.0.12 + vite: ^6.3.0 '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -5275,7 +5294,7 @@ packages: resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 - vite: npm:rolldown-vite@7.0.12 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true @@ -8637,6 +8656,7 @@ packages: multer@1.4.4-lts.1: resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} engines: {node: '>= 6.0.0'} + deprecated: Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version. murmurhash3js@3.0.1: resolution: {integrity: sha512-KL8QYUaxq7kUbcl0Yto51rMcYt7E/4N4BG3/c96Iqw1PQrTRspu8Cpx4TZ4Nunib1d4bEkIH3gjCYlP2RLBdow==} @@ -10226,6 +10246,7 @@ packages: superagent@9.0.2: resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} engines: {node: '>=14.18.0'} + deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net supertest@6.3.4: resolution: {integrity: sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==} @@ -10859,12 +10880,12 @@ packages: vite-dev-rpc@1.1.0: resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==} peerDependencies: - vite: npm:rolldown-vite@7.0.12 + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 vite-hot-client@2.1.0: resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} peerDependencies: - vite: npm:rolldown-vite@7.0.12 + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} @@ -10876,7 +10897,7 @@ packages: engines: {node: '>=14'} peerDependencies: '@nuxt/kit': '*' - vite: npm:rolldown-vite@7.0.12 + vite: ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: '@nuxt/kit': optional: true @@ -10884,7 +10905,47 @@ packages: vite-plugin-svgr@4.2.0: resolution: {integrity: sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==} peerDependencies: - vite: npm:rolldown-vite@7.0.12 + vite: ^2.6.0 || 3 || 4 || 5 + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} @@ -15319,7 +15380,7 @@ snapshots: prettier: 3.3.2 zod: 3.23.8 - '@tanstack/router-plugin@1.43.1': + '@tanstack/router-plugin@1.43.1(vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.37.0)(yaml@2.5.0))': dependencies: '@babel/core': 7.26.10 '@babel/generator': 7.24.7 @@ -15337,6 +15398,8 @@ snapshots: babel-dead-code-elimination: 1.0.5 unplugin: 1.10.2 zod: 3.23.8 + optionalDependencies: + vite: 6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.37.0)(yaml@2.5.0) transitivePeerDependencies: - supports-color @@ -15996,7 +16059,9 @@ snapshots: '@vercel/speed-insights@1.1.0': {} - '@vitejs/plugin-react-oxc@0.1.1': {} + '@vitejs/plugin-react-oxc@0.1.1(vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(terser@5.37.0)(yaml@2.5.0))': + dependencies: + vite: 6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.37.0)(yaml@2.5.0) '@vitest/expect@3.2.4': dependencies: @@ -17477,7 +17542,6 @@ snapshots: '@esbuild/win32-arm64': 0.25.1 '@esbuild/win32-ia32': 0.25.1 '@esbuild/win32-x64': 0.25.1 - optional: true escalade@3.1.2: {} @@ -20604,6 +20668,14 @@ snapshots: postcss: 8.5.3 ts-node: 10.9.2(@swc/core@1.11.21)(@types/node@20.17.11)(typescript@5.7.2) + postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@20.17.11)(typescript@5.9.3)): + dependencies: + lilconfig: 3.1.2 + yaml: 2.4.5 + optionalDependencies: + postcss: 8.5.3 + ts-node: 10.9.2(@swc/core@1.11.21)(@types/node@20.17.11)(typescript@5.9.3) + postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@24.9.2)(typescript@5.9.3)): dependencies: lilconfig: 3.1.2 @@ -21966,6 +22038,11 @@ snapshots: dependencies: postcss: 8.5.3 + sugarss@4.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + optional: true + superagent@9.0.2: dependencies: component-emitter: 1.3.1 @@ -22018,6 +22095,33 @@ snapshots: tailwind-merge@3.3.1: {} + tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@20.17.11)(typescript@5.9.3)): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.7 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.1 + postcss: 8.5.3 + postcss-import: 15.1.0(postcss@8.5.3) + postcss-js: 4.0.1(postcss@8.5.3) + postcss-load-config: 4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@20.17.11)(typescript@5.9.3)) + postcss-nested: 6.0.1(postcss@8.5.3) + postcss-selector-parser: 6.1.0 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.11.21)(@types/node@24.9.2)(typescript@5.9.3)): dependencies: '@alloc/quick-lru': 5.2.0 @@ -22254,6 +22358,27 @@ snapshots: optionalDependencies: '@swc/core': 1.11.21 + ts-node@10.9.2(@swc/core@1.11.21)(@types/node@20.17.11)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.17.11 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.11.21 + optional: true + ts-node@10.9.2(@swc/core@1.11.21)(@types/node@24.9.2)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -22623,12 +22748,15 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-dev-rpc@1.1.0: + vite-dev-rpc@1.1.0(vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(terser@5.37.0)(yaml@2.5.0)): dependencies: birpc: 2.6.1 - vite-hot-client: 2.1.0 + vite: 6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.37.0)(yaml@2.5.0) + vite-hot-client: 2.1.0(vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(terser@5.37.0)(yaml@2.5.0)) - vite-hot-client@2.1.0: {} + vite-hot-client@2.1.0(vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(terser@5.37.0)(yaml@2.5.0)): + dependencies: + vite: 6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.37.0)(yaml@2.5.0) vite-node@3.2.4(@types/node@24.9.2)(esbuild@0.25.1)(jiti@2.6.1)(less@4.2.0)(sass@1.86.0)(stylus@0.62.0)(terser@5.37.0)(yaml@2.5.0): dependencies: @@ -22651,7 +22779,7 @@ snapshots: - tsx - yaml - vite-plugin-inspect@11.3.3: + vite-plugin-inspect@11.3.3(vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(terser@5.37.0)(yaml@2.5.0)): dependencies: ansis: 4.1.0 debug: 4.4.3 @@ -22661,20 +22789,62 @@ snapshots: perfect-debounce: 2.0.0 sirv: 3.0.1 unplugin-utils: 0.3.1 - vite-dev-rpc: 1.1.0 + vite: 6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.37.0)(yaml@2.5.0) + vite-dev-rpc: 1.1.0(vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(terser@5.37.0)(yaml@2.5.0)) transitivePeerDependencies: - supports-color - vite-plugin-svgr@4.2.0(rollup@4.40.1)(typescript@5.9.3): + vite-plugin-svgr@4.2.0(rollup@4.40.1)(typescript@5.9.3)(vite@6.4.1(@types/node@24.9.2)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.6))(terser@5.37.0)(yaml@2.5.0)): dependencies: '@rollup/pluginutils': 5.1.0(rollup@4.40.1) '@svgr/core': 8.1.0(typescript@5.9.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) + vite: 6.4.1(@types/node@24.9.2)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.6))(terser@5.37.0)(yaml@2.5.0) transitivePeerDependencies: - rollup - supports-color - typescript + vite@6.4.1(@types/node@20.17.11)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.37.0)(yaml@2.5.0): + dependencies: + esbuild: 0.25.1 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.40.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.17.11 + fsevents: 2.3.3 + jiti: 2.6.1 + less: 4.2.0 + lightningcss: 1.30.2 + sass: 1.86.0 + stylus: 0.62.0 + sugarss: 4.0.1(postcss@8.5.3) + terser: 5.37.0 + yaml: 2.5.0 + + vite@6.4.1(@types/node@24.9.2)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass@1.86.0)(stylus@0.62.0)(sugarss@4.0.1(postcss@8.5.6))(terser@5.37.0)(yaml@2.5.0): + dependencies: + esbuild: 0.25.1 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.40.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.9.2 + fsevents: 2.3.3 + jiti: 2.6.1 + less: 4.2.0 + lightningcss: 1.30.2 + sass: 1.86.0 + stylus: 0.62.0 + sugarss: 4.0.1(postcss@8.5.6) + terser: 5.37.0 + yaml: 2.5.0 + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.9.2)(@vitest/ui@3.2.4)(esbuild@0.25.1)(jiti@2.6.1)(less@4.2.0)(sass@1.86.0)(stylus@0.62.0)(terser@5.37.0)(yaml@2.5.0): dependencies: '@types/chai': 5.2.2 From 6a944a6f01bfc1abb54206d57d9436bda66c3baa Mon Sep 17 00:00:00 2001 From: Andreas Heissenberger Date: Fri, 14 Nov 2025 10:33:42 +0100 Subject: [PATCH 2/7] fix: improve source map logging in SSR handler --- packages/react-server/lib/start/action.mjs | 2 -- packages/react-server/lib/start/ssr-handler.mjs | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react-server/lib/start/action.mjs b/packages/react-server/lib/start/action.mjs index 234009ad..f61d86da 100644 --- a/packages/react-server/lib/start/action.mjs +++ b/packages/react-server/lib/start/action.mjs @@ -46,8 +46,6 @@ async function worker(root, options, config) { await runtime_init$(async () => { runtime$(CONFIG_CONTEXT, config); - // Check if sourcemaps are available by looking for .map files in build output - // This will be set to true if sourcemaps were generated during build runtime$(SOURCEMAP_ENABLED, false); const logger = await createLogger(configRoot); const server = await createServer(root, options); diff --git a/packages/react-server/lib/start/ssr-handler.mjs b/packages/react-server/lib/start/ssr-handler.mjs index ca982ad3..7b15dbbf 100644 --- a/packages/react-server/lib/start/ssr-handler.mjs +++ b/packages/react-server/lib/start/ssr-handler.mjs @@ -123,10 +123,9 @@ export default async function ssrHandler(root, options = {}) { hookRequire: false, handleUncaughtExceptions: false, }); - console.log("[SourceMap] Source map support enabled"); + logger.log("Source map support enabled"); } catch { // Sourcemaps not available - console.log("[SourceMap] No source maps found"); } runtime$(SOURCEMAP_ENABLED, sourcemapEnabled); From 8bacc49012e6f02e8901157b30e0ad7a25d826f4 Mon Sep 17 00:00:00 2001 From: Andreas Heissenberger Date: Fri, 14 Nov 2025 10:37:49 +0100 Subject: [PATCH 3/7] fix: use info logger and fix text --- packages/react-server/lib/start/ssr-handler.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-server/lib/start/ssr-handler.mjs b/packages/react-server/lib/start/ssr-handler.mjs index 7b15dbbf..fc39411d 100644 --- a/packages/react-server/lib/start/ssr-handler.mjs +++ b/packages/react-server/lib/start/ssr-handler.mjs @@ -123,7 +123,7 @@ export default async function ssrHandler(root, options = {}) { hookRequire: false, handleUncaughtExceptions: false, }); - logger.log("Source map support enabled"); + logger.info("Source map stack trace mapping support enabled"); } catch { // Sourcemaps not available } From bbb4b62587e2097959b5fb0b4a50aafb82c6574b Mon Sep 17 00:00:00 2001 From: Andreas Heissenberger Date: Mon, 17 Nov 2025 12:21:27 +0100 Subject: [PATCH 4/7] feat: switch flag to SOURCEMAP_SUPPORT --- SOURCEMAP_IMPLEMENTATION.md | 133 ++++++++++++++++++ packages/react-server/lib/start/action.mjs | 4 +- .../react-server/lib/start/ssr-handler.mjs | 4 +- packages/react-server/server/symbols.mjs | 2 +- 4 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 SOURCEMAP_IMPLEMENTATION.md diff --git a/SOURCEMAP_IMPLEMENTATION.md b/SOURCEMAP_IMPLEMENTATION.md new file mode 100644 index 00000000..f6d44900 --- /dev/null +++ b/SOURCEMAP_IMPLEMENTATION.md @@ -0,0 +1,133 @@ +# Source Map Support for Production Error Stack Traces + +## Overview + +This implementation adds support for converting production error stack traces back to their original source locations using source maps. When you build with the `--sourcemap` flag, error messages in production will show the original file locations and line numbers instead of the compiled/bundled locations. + +## How It Works + +### 1. Source Map Detection + +When the server starts in production mode, it checks if source map files (`.map`) exist alongside the built files in the output directory (default: `.react-server/server/`). If found, the `SOURCEMAP_SUPPORT` flag is set in the runtime context. + +**File**: `lib/start/ssr-handler.mjs` +- Checks for `.map` files during initialization +- Sets the `SOURCEMAP_SUPPORT` runtime flag + +### 2. Stack Trace Conversion + +The `source-map-support` package is dynamically imported and installed at server startup when source maps are detected. It automatically hooks into Node.js's `Error.prepareStackTrace` to convert stack traces: + +**Package**: `source-map-support` (dynamically loaded) +- Only imported when `.map` files are detected +- Automatically loads `.map` files from the file system +- Caches parsed source maps for performance +- Hooks into Node.js error handling +- Transparently converts all stack traces system-wide + +### 3. Error Logging Integration + +Once `source-map-support` is installed, all errors throughout the application automatically have their stack traces converted. No manual conversion is needed at individual error points. + +**Files Modified**: +- `lib/start/ssr-handler.mjs` - Installs source-map-support at startup + +## Usage + +### Building with Source Maps + +```bash +# Build with separate source map files +npx react-server build --sourcemap + +# Build with inline source maps +npx react-server build --sourcemap inline + +# Build with hidden source maps (not referenced in code) +npx react-server build --sourcemap hidden +``` + +### Starting the Production Server + +```bash +npx react-server start +``` + +The server will automatically detect if source maps are available and use them for error reporting. + +## Example + +### Without Source Maps + +``` +Error: Something went wrong + at /app/.react-server/server/index.mjs:1234:56 + at /app/.react-server/server/render.mjs:789:12 +``` + +### With Source Maps + +``` +Error: Something went wrong + at /app/src/components/MyComponent.jsx:45:23 + at /app/src/pages/index.jsx:12:8 +``` + +## Implementation Details + +### Modified Files + +1. **server/symbols.mjs** + - Added `SOURCEMAP_SUPPORT` symbol for runtime flag + +2. **lib/start/ssr-handler.mjs** + - Detects source map availability on startup (checks for `.map` files) + - Dynamically imports `source-map-support` only when source maps are detected + - Sets `SOURCEMAP_SUPPORT` runtime flag + - Installs `source-map-support` when source maps are available + +### Dependencies Added + +- `source-map-support` - Automatically converts stack traces using source maps (dynamically loaded only when needed) + +## Performance Considerations + +- **Dynamic Loading**: `source-map-support` is only imported when source maps are detected, reducing memory footprint in development and when source maps aren't available +- **One-time Check**: Source map detection happens once at server startup, not on every request +- **Efficient Caching**: `source-map-support` caches parsed source maps in memory after first load +- **Minimal Overhead**: Hooks are installed once at startup with negligible performance impact +- **Graceful Fallback**: Falls back to original stack trace if source map lookup fails + +## Conditional Execution + +The source map conversion **only happens when**: +1. The build was created with `--sourcemap` flag +2. The `.map` files exist in the output directory +3. Running in production mode (not dev mode) +4. An error occurs + +## Testing + +To test the implementation: + +1. Build with source maps: + ```bash + cd examples/hello-world + npx react-server build --sourcemap + ``` + +2. Start the production server: + ```bash + npx react-server start + ``` + +3. Trigger an error in your application and check the console output + +4. Verify that stack traces show original source locations + +## Notes + +- Dev mode already has source map support through Vite's module graph +- This implementation is specifically for production builds +- Source maps should be kept secure and not deployed to public-facing servers if they contain sensitive information +- The `--sourcemap hidden` option generates source maps but doesn't reference them in the code (useful for error tracking without exposing sources) diff --git a/packages/react-server/lib/start/action.mjs b/packages/react-server/lib/start/action.mjs index f61d86da..0b138175 100644 --- a/packages/react-server/lib/start/action.mjs +++ b/packages/react-server/lib/start/action.mjs @@ -14,7 +14,7 @@ import { CONFIG_ROOT, LOGGER_CONTEXT, SERVER_CONTEXT, - SOURCEMAP_ENABLED, + SOURCEMAP_SUPPORT, } from "../../server/symbols.mjs"; import { formatDuration } from "../utils/format.mjs"; import getServerAddresses from "../utils/server-address.mjs"; @@ -46,7 +46,7 @@ async function worker(root, options, config) { await runtime_init$(async () => { runtime$(CONFIG_CONTEXT, config); - runtime$(SOURCEMAP_ENABLED, false); + runtime$(SOURCEMAP_SUPPORT, false); const logger = await createLogger(configRoot); const server = await createServer(root, options); const { port, listenerHost } = getServerConfig(configRoot, options); diff --git a/packages/react-server/lib/start/ssr-handler.mjs b/packages/react-server/lib/start/ssr-handler.mjs index fc39411d..493e0197 100644 --- a/packages/react-server/lib/start/ssr-handler.mjs +++ b/packages/react-server/lib/start/ssr-handler.mjs @@ -39,7 +39,7 @@ import { RENDER_CONTEXT, RENDER_STREAM, SERVER_CONTEXT, - SOURCEMAP_ENABLED, + SOURCEMAP_SUPPORT, STYLES_CONTEXT, } from "../../server/symbols.mjs"; import { ContextManager } from "../async-local-storage.mjs"; @@ -127,7 +127,7 @@ export default async function ssrHandler(root, options = {}) { } catch { // Sourcemaps not available } - runtime$(SOURCEMAP_ENABLED, sourcemapEnabled); + runtime$(SOURCEMAP_SUPPORT, sourcemapEnabled); const importMap = configRoot.importMap || configRoot.resolve?.shared diff --git a/packages/react-server/server/symbols.mjs b/packages/react-server/server/symbols.mjs index 9ba129f7..c9e5202a 100644 --- a/packages/react-server/server/symbols.mjs +++ b/packages/react-server/server/symbols.mjs @@ -26,7 +26,7 @@ export const SSR_CONTROLLER = Symbol.for("SSR_CONTROLLER"); export const ROUTE_MATCH = Symbol.for("ROUTE_MATCH"); export const STYLES_CONTEXT = Symbol.for("STYLES_CONTEXT"); export const BUILD_OPTIONS = Symbol.for("BUILD_OPTIONS"); -export const SOURCEMAP_ENABLED = Symbol.for("SOURCEMAP_ENABLED"); +export const SOURCEMAP_SUPPORT = Symbol.for("SOURCEMAP_SUPPORT"); export const ACTION_CONTEXT = Symbol.for("ACTION_CONTEXT"); export const RELOAD = Symbol.for("RELOAD"); export const RENDER = Symbol.for("RENDER"); From 275a49767a1f96e74110ea6b8f0c09e11e9a34af Mon Sep 17 00:00:00 2001 From: Andreas Heissenberger Date: Mon, 17 Nov 2025 18:15:19 +0100 Subject: [PATCH 5/7] fix: replace runtime sourcemap detection with build meta data --- packages/react-server/lib/build/server.mjs | 12 ++++++ packages/react-server/lib/start/action.mjs | 2 - .../react-server/lib/start/ssr-handler.mjs | 41 ++++++++++++------- packages/react-server/server/symbols.mjs | 1 - 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/packages/react-server/lib/build/server.mjs b/packages/react-server/lib/build/server.mjs index 04dbd317..c21864e4 100644 --- a/packages/react-server/lib/build/server.mjs +++ b/packages/react-server/lib/build/server.mjs @@ -531,5 +531,17 @@ export default async function serverBuild(root, options) { "utf8" ); } + + // Write build metadata with sourcemap option + await writeFile( + join(cwd, options.outDir, "server/build-meta.json"), + JSON.stringify({ + sourcemap: + options.sourcemap !== false ? options.sourcemap || true : false, + buildTime: new Date().toISOString(), + }), + "utf8" + ); + return true; } diff --git a/packages/react-server/lib/start/action.mjs b/packages/react-server/lib/start/action.mjs index 0b138175..e7251006 100644 --- a/packages/react-server/lib/start/action.mjs +++ b/packages/react-server/lib/start/action.mjs @@ -14,7 +14,6 @@ import { CONFIG_ROOT, LOGGER_CONTEXT, SERVER_CONTEXT, - SOURCEMAP_SUPPORT, } from "../../server/symbols.mjs"; import { formatDuration } from "../utils/format.mjs"; import getServerAddresses from "../utils/server-address.mjs"; @@ -46,7 +45,6 @@ async function worker(root, options, config) { await runtime_init$(async () => { runtime$(CONFIG_CONTEXT, config); - runtime$(SOURCEMAP_SUPPORT, false); const logger = await createLogger(configRoot); const server = await createServer(root, options); const { port, listenerHost } = getServerConfig(configRoot, options); diff --git a/packages/react-server/lib/start/ssr-handler.mjs b/packages/react-server/lib/start/ssr-handler.mjs index 493e0197..f1c05f32 100644 --- a/packages/react-server/lib/start/ssr-handler.mjs +++ b/packages/react-server/lib/start/ssr-handler.mjs @@ -1,7 +1,7 @@ -import { access, constants as fsConstants } from "node:fs/promises"; +import { existsSync, readFileSync } from "node:fs"; import { createRequire, register } from "node:module"; import { join, relative } from "node:path"; -import { pathToFileURL } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { init$ as cache_init$, useCache } from "../../cache/index.mjs"; import { context$, ContextStorage, getContext } from "../../server/context.mjs"; @@ -39,7 +39,6 @@ import { RENDER_CONTEXT, RENDER_STREAM, SERVER_CONTEXT, - SOURCEMAP_SUPPORT, STYLES_CONTEXT, } from "../../server/symbols.mjs"; import { ContextManager } from "../async-local-storage.mjs"; @@ -85,12 +84,16 @@ export default async function ssrHandler(root, options = {}) { } const [ { render }, + { default: buildMetadata }, { default: Component, init$: root_init$ }, { default: GlobalErrorComponent }, { default: ErrorBoundary }, rscSerializer, ] = await Promise.all([ import(pathToFileURL(entryModule)), + import(pathToFileURL(join(cwd, options.outDir, "server/build-meta.json")), { + with: { type: "json" }, + }), import(pathToFileURL(rootModule)), import(pathToFileURL(globalErrorModule)), errorBoundary @@ -111,23 +114,33 @@ export default async function ssrHandler(root, options = {}) { const moduleCacheStorage = new ContextManager(); await module_loader_init$(moduleLoader, moduleCacheStorage); - // Check if sourcemaps are available by checking if entryModule has a .map file - let sourcemapEnabled = false; - try { - await access(`${entryModule}.map`, fsConstants.R_OK); - sourcemapEnabled = true; - // Dynamically import and install source-map-support only when source maps are available + if (buildMetadata.sourcemap && buildMetadata.sourcemap !== false) { const { default: sourceMapSupport } = await import("source-map-support"); sourceMapSupport.install({ environment: "node", - hookRequire: false, + hookRequire: buildMetadata.sourcemap === "inline", handleUncaughtExceptions: false, + retrieveSourceMap: + buildMetadata.sourcemap === "hidden" + ? (source) => { + logger.info(`Retrieving source map for ${source}`); + if (source.startsWith("file:")) { + const mapFilePath = fileURLToPath(source) + ".map"; + if (existsSync(mapFilePath)) { + return { + url: source, + map: readFileSync(mapFilePath, "utf8"), + }; + } + } + return null; + } + : undefined, }); - logger.info("Source map stack trace mapping support enabled"); - } catch { - // Sourcemaps not available + logger.info( + `Source map (${buildMetadata.sourcemap === true ? "file" : buildMetadata.sourcemap}) stack trace mapping support enabled` + ); } - runtime$(SOURCEMAP_SUPPORT, sourcemapEnabled); const importMap = configRoot.importMap || configRoot.resolve?.shared diff --git a/packages/react-server/server/symbols.mjs b/packages/react-server/server/symbols.mjs index c9e5202a..882ba011 100644 --- a/packages/react-server/server/symbols.mjs +++ b/packages/react-server/server/symbols.mjs @@ -26,7 +26,6 @@ export const SSR_CONTROLLER = Symbol.for("SSR_CONTROLLER"); export const ROUTE_MATCH = Symbol.for("ROUTE_MATCH"); export const STYLES_CONTEXT = Symbol.for("STYLES_CONTEXT"); export const BUILD_OPTIONS = Symbol.for("BUILD_OPTIONS"); -export const SOURCEMAP_SUPPORT = Symbol.for("SOURCEMAP_SUPPORT"); export const ACTION_CONTEXT = Symbol.for("ACTION_CONTEXT"); export const RELOAD = Symbol.for("RELOAD"); export const RENDER = Symbol.for("RENDER"); From 45b9569d59449779d65968072829b0b705d32e32 Mon Sep 17 00:00:00 2001 From: Andreas Heissenberger Date: Mon, 17 Nov 2025 18:17:12 +0100 Subject: [PATCH 6/7] feat: stack trace for react server components in production --- packages/react-server/server/render-rsc.jsx | 45 +++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/react-server/server/render-rsc.jsx b/packages/react-server/server/render-rsc.jsx index f5873aa4..82181a00 100644 --- a/packages/react-server/server/render-rsc.jsx +++ b/packages/react-server/server/render-rsc.jsx @@ -52,6 +52,34 @@ import { import { ServerFunctionNotFoundError } from "./action-state.mjs"; import { cwd } from "../lib/sys.mjs"; +/** + * Helper to log errors with source-mapped stack traces. + * Forces Error.prepareStackTrace to run if installed by source-map-support. + */ +function logErrorWithStackTrace(logger, error) { + if (!error) return; + if ( + error.stack.startsWith( + "Error: An error occurred in the Server Components render." + ) + ) + return; + + // Access .stack to trigger Error.prepareStackTrace if it's been set + // source-map-support hooks into this to provide mapped stack traces + const stack = error.stack; + + // Log the error with its prepared stack trace + if (logger?.error) { + logger.error(error); + } else { + console.error(error); + } + + // Return the stack for potential further processing + return stack; +} + const serverReferenceMap = new Proxy( {}, { @@ -522,6 +550,10 @@ export async function render(Component, props = {}, options = {}) { temporaryReferences, onError(e) { hasError = true; + // Log error with source-mapped stack trace + if (import.meta.env.PROD) { + logErrorWithStackTrace(logger, e); + } const redirect = getContext(REDIRECT_CONTEXT); if (redirect?.response) { return `Location=${redirect.response.headers.get("location")}`; @@ -660,6 +692,10 @@ export async function render(Component, props = {}, options = {}) { temporaryReferences, onError(e) { hasError = true; + // Log error with source-mapped stack trace + if (import.meta.env.PROD) { + logErrorWithStackTrace(logger, e); + } const redirect = getContext(REDIRECT_CONTEXT); if (redirect?.response) { return resolve(redirect.response); @@ -763,7 +799,8 @@ export async function render(Component, props = {}, options = {}) { if (!e.digest && digest) { e.digest = digest; } - (logger ?? console).error(e); + // Log error with source-mapped stack trace + logErrorWithStackTrace(logger, e); hasError = true; if (!isStarted) { ContextStorage.run(contextStore, async () => { @@ -822,13 +859,15 @@ export async function render(Component, props = {}, options = {}) { ); } } catch (e) { - logger.error(e); + // Log error with source-mapped stack trace + logErrorWithStackTrace(logger, e); getContext(ERROR_CONTEXT)?.(e)?.then(resolve, reject); } }); return streaming; } catch (e) { - logger.error(e); + // Log error with source-mapped stack trace + logErrorWithStackTrace(logger, e); return new Response(null, { status: 500, statusText: "Internal Server Error", From 0de6b4ac442f41c465a391d1122f3d04c70d2914 Mon Sep 17 00:00:00 2001 From: Andreas Heissenberger Date: Mon, 17 Nov 2025 18:36:48 +0100 Subject: [PATCH 7/7] fix: remove not required file --- SOURCEMAP_IMPLEMENTATION.md | 133 ------------------------------------ 1 file changed, 133 deletions(-) delete mode 100644 SOURCEMAP_IMPLEMENTATION.md diff --git a/SOURCEMAP_IMPLEMENTATION.md b/SOURCEMAP_IMPLEMENTATION.md deleted file mode 100644 index f6d44900..00000000 --- a/SOURCEMAP_IMPLEMENTATION.md +++ /dev/null @@ -1,133 +0,0 @@ -# Source Map Support for Production Error Stack Traces - -## Overview - -This implementation adds support for converting production error stack traces back to their original source locations using source maps. When you build with the `--sourcemap` flag, error messages in production will show the original file locations and line numbers instead of the compiled/bundled locations. - -## How It Works - -### 1. Source Map Detection - -When the server starts in production mode, it checks if source map files (`.map`) exist alongside the built files in the output directory (default: `.react-server/server/`). If found, the `SOURCEMAP_SUPPORT` flag is set in the runtime context. - -**File**: `lib/start/ssr-handler.mjs` -- Checks for `.map` files during initialization -- Sets the `SOURCEMAP_SUPPORT` runtime flag - -### 2. Stack Trace Conversion - -The `source-map-support` package is dynamically imported and installed at server startup when source maps are detected. It automatically hooks into Node.js's `Error.prepareStackTrace` to convert stack traces: - -**Package**: `source-map-support` (dynamically loaded) -- Only imported when `.map` files are detected -- Automatically loads `.map` files from the file system -- Caches parsed source maps for performance -- Hooks into Node.js error handling -- Transparently converts all stack traces system-wide - -### 3. Error Logging Integration - -Once `source-map-support` is installed, all errors throughout the application automatically have their stack traces converted. No manual conversion is needed at individual error points. - -**Files Modified**: -- `lib/start/ssr-handler.mjs` - Installs source-map-support at startup - -## Usage - -### Building with Source Maps - -```bash -# Build with separate source map files -npx react-server build --sourcemap - -# Build with inline source maps -npx react-server build --sourcemap inline - -# Build with hidden source maps (not referenced in code) -npx react-server build --sourcemap hidden -``` - -### Starting the Production Server - -```bash -npx react-server start -``` - -The server will automatically detect if source maps are available and use them for error reporting. - -## Example - -### Without Source Maps - -``` -Error: Something went wrong - at /app/.react-server/server/index.mjs:1234:56 - at /app/.react-server/server/render.mjs:789:12 -``` - -### With Source Maps - -``` -Error: Something went wrong - at /app/src/components/MyComponent.jsx:45:23 - at /app/src/pages/index.jsx:12:8 -``` - -## Implementation Details - -### Modified Files - -1. **server/symbols.mjs** - - Added `SOURCEMAP_SUPPORT` symbol for runtime flag - -2. **lib/start/ssr-handler.mjs** - - Detects source map availability on startup (checks for `.map` files) - - Dynamically imports `source-map-support` only when source maps are detected - - Sets `SOURCEMAP_SUPPORT` runtime flag - - Installs `source-map-support` when source maps are available - -### Dependencies Added - -- `source-map-support` - Automatically converts stack traces using source maps (dynamically loaded only when needed) - -## Performance Considerations - -- **Dynamic Loading**: `source-map-support` is only imported when source maps are detected, reducing memory footprint in development and when source maps aren't available -- **One-time Check**: Source map detection happens once at server startup, not on every request -- **Efficient Caching**: `source-map-support` caches parsed source maps in memory after first load -- **Minimal Overhead**: Hooks are installed once at startup with negligible performance impact -- **Graceful Fallback**: Falls back to original stack trace if source map lookup fails - -## Conditional Execution - -The source map conversion **only happens when**: -1. The build was created with `--sourcemap` flag -2. The `.map` files exist in the output directory -3. Running in production mode (not dev mode) -4. An error occurs - -## Testing - -To test the implementation: - -1. Build with source maps: - ```bash - cd examples/hello-world - npx react-server build --sourcemap - ``` - -2. Start the production server: - ```bash - npx react-server start - ``` - -3. Trigger an error in your application and check the console output - -4. Verify that stack traces show original source locations - -## Notes - -- Dev mode already has source map support through Vite's module graph -- This implementation is specifically for production builds -- Source maps should be kept secure and not deployed to public-facing servers if they contain sensitive information -- The `--sourcemap hidden` option generates source maps but doesn't reference them in the code (useful for error tracking without exposing sources)