@@ -65,112 +65,131 @@ const nextConfig: NextConfig = {
6565 static : 300
6666 }
6767 } ,
68- ...( useLegacyWebpackTweaks
68+ // Coverage builds need full source maps on BOTH client and server
69+ // bundles. `productionBrowserSourceMaps: true` (above) handles client;
70+ // for the server bundle Next 15 omits sourcemaps in production by
71+ // default, so we force `devtool = source-map` via a webpack hook.
72+ // Without this, `c8 report` over NODE_V8_COVERAGE has no way to map
73+ // compiled `.next/server/app/.../page.js` back to its `.tsx` source
74+ // and silently produces near-empty server coverage.
75+ ...( coverageBuild
6976 ? {
70- // Keep legacy memory-optimized webpack behavior available via NEXT_BUNDLING_PROFILE=legacy.
71- webpack : ( config , { isServer, dev } ) => {
72- if ( config . cache && ! dev ) {
73- config . cache = {
74- ...config . cache ,
75- maxMemoryGenerations : 1
76- } ;
77- }
78-
79- if ( ! isServer ) {
80- config . optimization = {
81- ...config . optimization ,
82- moduleIds : "deterministic" ,
83- splitChunks : {
84- chunks : "all" ,
85- maxInitialRequests : 25 ,
86- maxAsyncRequests : 30 ,
87- cacheGroups : {
88- default : false ,
89- monaco : {
90- name : "monaco-editor" ,
91- test : / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] ( @ m o n a c o - e d i t o r | m o n a c o - e d i t o r | m o n a c o - y a m l ) [ \\ / ] / ,
92- priority : 20 ,
93- reuseExistingChunk : true ,
94- enforce : true
95- } ,
96- chakra : {
97- name : "chakra-ui" ,
98- test : / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] @ c h a k r a - u i [ \\ / ] / ,
99- priority : 15 ,
100- reuseExistingChunk : true ,
101- enforce : true
102- } ,
103- charts : {
104- name : "charts" ,
105- test : / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] ( r e c h a r t s | @ c h a k r a - u i \/ c h a r t s ) [ \\ / ] / ,
106- priority : 10 ,
107- reuseExistingChunk : true ,
108- enforce : true
109- } ,
110- mdEditor : {
111- name : "md-editor" ,
112- test : / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] @ u i w [ \\ / ] r e a c t - m d - e d i t o r [ \\ / ] / ,
113- priority : 10 ,
114- reuseExistingChunk : true ,
115- enforce : true
116- } ,
117- mathjs : {
118- name : "mathjs" ,
119- test : / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] m a t h j s [ \\ / ] / ,
120- priority : 10 ,
121- reuseExistingChunk : true ,
122- enforce : true
77+ webpack : ( config : { devtool ?: string } , ctx : { isServer : boolean } ) => {
78+ // Only override server bundle. Client bundles get
79+ // sourcemaps via `productionBrowserSourceMaps: true`
80+ // above; setting devtool twice (here + the option)
81+ // confuses Next's webpack pipeline and produces an
82+ // empty `.next/static` directory.
83+ if ( ctx . isServer ) config . devtool = "source-map" ;
84+ return config ;
85+ }
86+ }
87+ : useLegacyWebpackTweaks
88+ ? {
89+ // Keep legacy memory-optimized webpack behavior available via NEXT_BUNDLING_PROFILE=legacy.
90+ webpack : ( config , { isServer, dev } ) => {
91+ if ( config . cache && ! dev ) {
92+ config . cache = {
93+ ...config . cache ,
94+ maxMemoryGenerations : 1
95+ } ;
96+ }
97+
98+ if ( ! isServer ) {
99+ config . optimization = {
100+ ...config . optimization ,
101+ moduleIds : "deterministic" ,
102+ splitChunks : {
103+ chunks : "all" ,
104+ maxInitialRequests : 25 ,
105+ maxAsyncRequests : 30 ,
106+ cacheGroups : {
107+ default : false ,
108+ monaco : {
109+ name : "monaco-editor" ,
110+ test : / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] ( @ m o n a c o - e d i t o r | m o n a c o - e d i t o r | m o n a c o - y a m l ) [ \\ / ] / ,
111+ priority : 20 ,
112+ reuseExistingChunk : true ,
113+ enforce : true
114+ } ,
115+ chakra : {
116+ name : "chakra-ui" ,
117+ test : / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] @ c h a k r a - u i [ \\ / ] / ,
118+ priority : 15 ,
119+ reuseExistingChunk : true ,
120+ enforce : true
121+ } ,
122+ charts : {
123+ name : "charts" ,
124+ test : / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] ( r e c h a r t s | @ c h a k r a - u i \/ c h a r t s ) [ \\ / ] / ,
125+ priority : 10 ,
126+ reuseExistingChunk : true ,
127+ enforce : true
128+ } ,
129+ mdEditor : {
130+ name : "md-editor" ,
131+ test : / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] @ u i w [ \\ / ] r e a c t - m d - e d i t o r [ \\ / ] / ,
132+ priority : 10 ,
133+ reuseExistingChunk : true ,
134+ enforce : true
135+ } ,
136+ mathjs : {
137+ name : "mathjs" ,
138+ test : / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] m a t h j s [ \\ / ] / ,
139+ priority : 10 ,
140+ reuseExistingChunk : true ,
141+ enforce : true
142+ }
123143 }
124144 }
125- }
126- } ;
127- }
145+ } ;
146+ }
128147
129- if ( config . optimization ?. minimizer ) {
130- config . optimization . minimizer = config . optimization . minimizer . map ( ( plugin : unknown ) => {
131- if ( ! plugin || typeof plugin !== "object" || ! ( "constructor" in plugin ) ) {
132- return plugin ;
133- }
148+ if ( config . optimization ?. minimizer ) {
149+ config . optimization . minimizer = config . optimization . minimizer . map ( ( plugin : unknown ) => {
150+ if ( ! plugin || typeof plugin !== "object" || ! ( "constructor" in plugin ) ) {
151+ return plugin ;
152+ }
134153
135- const pluginName = plugin . constructor . name ;
154+ const pluginName = plugin . constructor . name ;
136155
137- if ( pluginName === "SwcMinify" ) {
138- return plugin ;
139- }
140-
141- if ( pluginName === "TerserPlugin" ) {
142- const terserPlugin = plugin as {
143- options ?: { parallel ?: boolean ; terserOptions ?: { compress ?: { passes ?: number } } } ;
144- } ;
145- if ( terserPlugin . options ) {
146- terserPlugin . options . parallel = false ;
147- if ( terserPlugin . options . terserOptions ?. compress ) {
148- terserPlugin . options . terserOptions . compress . passes = 1 ;
156+ if ( pluginName === "SwcMinify" ) {
157+ return plugin ;
158+ }
159+
160+ if ( pluginName === "TerserPlugin" ) {
161+ const terserPlugin = plugin as {
162+ options ?: { parallel ?: boolean ; terserOptions ?: { compress ?: { passes ?: number } } } ;
163+ } ;
164+ if ( terserPlugin . options ) {
165+ terserPlugin . options . parallel = false ;
166+ if ( terserPlugin . options . terserOptions ?. compress ) {
167+ terserPlugin . options . terserOptions . compress . passes = 1 ;
168+ }
149169 }
170+ return plugin ;
150171 }
151- return plugin ;
152- }
153172
154- if ( pluginName === "CssMinimizerPlugin" ) {
155- const cssPlugin = plugin as { options ?: { parallel ?: boolean } } ;
156- if ( cssPlugin . options ) {
157- cssPlugin . options . parallel = false ;
173+ if ( pluginName === "CssMinimizerPlugin" ) {
174+ const cssPlugin = plugin as { options ?: { parallel ?: boolean } } ;
175+ if ( cssPlugin . options ) {
176+ cssPlugin . options . parallel = false ;
177+ }
178+ return plugin ;
158179 }
180+
159181 return plugin ;
160- }
182+ } ) ;
183+ }
161184
162- return plugin ;
163- } ) ;
164- }
185+ if ( config . resolve ) {
186+ config . resolve . cache = false ;
187+ }
165188
166- if ( config . resolve ) {
167- config . resolve . cache = false ;
189+ return config ;
168190 }
169-
170- return config ;
171191 }
172- }
173- : { } )
192+ : { } )
174193} ;
175194
176195// Skip Sentry webpack integration when DSN is unset (local dev) or explicitly disabled (CI speed).
0 commit comments