@@ -193,6 +193,14 @@ export interface StartOptions {
193193 * @default undefined (probe env.ts / env.js; off when absent)
194194 */
195195 env ?: boolean | string ;
196+ /**
197+ * Add the default production error boundary to generated entries.
198+ * Disable this when application middleware owns error handling. Authored
199+ * entries are unaffected.
200+ *
201+ * @default true
202+ */
203+ errorBoundary ?: boolean ;
196204 /**
197205 * Let a host integration own the server environment — build wiring and
198206 * HTTP serving alike. The plugin skips its start-mode server-build config and
@@ -248,6 +256,7 @@ const RESOLVED_DEV_STYLES_ID = '\0' + DEV_STYLES_ID;
248256const ENTRY_SERVER_ID = 'virtual:solid-ssr-entry-server.tsx' ;
249257const ENTRY_CLIENT_ID = 'virtual:solid-ssr-entry-client.tsx' ;
250258const DOCUMENT_ID = 'virtual:solid-ssr-document.tsx' ;
259+ const ERROR_BOUNDARY_ID = 'virtual:solid-ssr-error-boundary.tsx' ;
251260
252261const MANIFEST_ID = 'virtual:solid-manifest' ;
253262const SERVER_FUNCTION_HANDLER_ID = 'virtual:solid-server-function-handler' ;
@@ -401,6 +410,7 @@ export function startServe(
401410 // the server-function handler module either way). Everything is gated
402411 // codegen: with the option off, none of these imports exist anywhere.
403412 const serverComponents = ! ! internal . serverComponents ;
413+ const errorBoundary = options . errorBoundary !== false ;
404414 // `external` is server-mode-only (documented no-op in client mode, so a
405415 // host-integrated config survives the `ssr` boolean flip untouched).
406416 const externalServer = ! clientMode && ! ! options . external ;
@@ -476,6 +486,26 @@ export function startServe(
476486 ] . join ( '\n' ) ;
477487 }
478488
489+ function errorBoundaryImport ( ) : string [ ] {
490+ return isBuild && errorBoundary
491+ ? [ `import { DefaultErrorBoundary } from ${ JSON . stringify ( ERROR_BOUNDARY_ID ) } ;` ]
492+ : [ ] ;
493+ }
494+
495+ function documentTree ( root : string ) : string [ ] {
496+ return isBuild && errorBoundary
497+ ? [
498+ ` <DefaultErrorBoundary>` ,
499+ ` <Document>` ,
500+ ` <DefaultErrorBoundary>` ,
501+ ` <${ root } />` ,
502+ ` </DefaultErrorBoundary>` ,
503+ ` </Document>` ,
504+ ` </DefaultErrorBoundary>` ,
505+ ]
506+ : [ ` <Document>` , ` <${ root } />` , ` </Document>` ] ;
507+ }
508+
479509 function generatedEntryServerCode ( ) : string {
480510 if ( clientMode ) {
481511 // The client-mode shell: the document without the app. Rendered per
@@ -486,9 +516,18 @@ export function startServe(
486516 `import { renderToStream } from '@solidjs/web';` ,
487517 `import manifest from ${ JSON . stringify ( MANIFEST_ID ) } ;` ,
488518 `import Document from ${ JSON . stringify ( documentSpec ( ) ) } ;` ,
519+ ...errorBoundaryImport ( ) ,
489520 `` ,
490521 `export function render(request, context) {` ,
491- ` return renderToStream(() => <Document />, { manifest });` ,
522+ ` return renderToStream(() => (` ,
523+ ...( isBuild && errorBoundary
524+ ? [
525+ ` <DefaultErrorBoundary>` ,
526+ ` <Document />` ,
527+ ` </DefaultErrorBoundary>` ,
528+ ]
529+ : [ ` <Document />` ] ) ,
530+ ` ), { manifest });` ,
492531 `}` ,
493532 ] . join ( '\n' ) ;
494533 }
@@ -505,6 +544,7 @@ export function startServe(
505544 `import manifest from ${ JSON . stringify ( MANIFEST_ID ) } ;` ,
506545 `import Document from ${ JSON . stringify ( documentSpec ( ) ) } ;` ,
507546 `import App from ${ JSON . stringify ( app ) } ;` ,
547+ ...errorBoundaryImport ( ) ,
508548 ...( setupPath ? [ `import setup from ${ JSON . stringify ( setupPath ) } ;` ] : [ ] ) ,
509549 `` ,
510550 ...( setupPath
@@ -546,18 +586,14 @@ export function startServe(
546586 `` ,
547587 `function renderApp(Root) {` ,
548588 ` return renderToStream(() => (` ,
549- ` <Document>` ,
550- ` <Root />` ,
551- ` </Document>` ,
589+ ...documentTree ( 'Root' ) ,
552590 ` ), ${ streamOptions } );` ,
553591 `}` ,
554592 ]
555593 : [
556594 `export function render(request, context) {` ,
557595 ` return renderToStream(() => (` ,
558- ` <Document>` ,
559- ` <App />` ,
560- ` </Document>` ,
596+ ...documentTree ( 'App' ) ,
561597 ` ), ${ streamOptions } );` ,
562598 `}` ,
563599 ] ) ,
@@ -574,16 +610,22 @@ export function startServe(
574610 // complete when this runs.
575611 return [
576612 `import { render } from '@solidjs/web';` ,
613+ ...errorBoundaryImport ( ) ,
577614 `import App from ${ JSON . stringify ( app ) } ;` ,
578615 `` ,
579- `render(() => <App />, document.body);` ,
616+ `render(() => ${
617+ isBuild && errorBoundary
618+ ? '<DefaultErrorBoundary><App /></DefaultErrorBoundary>'
619+ : '<App />'
620+ } , document.body);`,
580621 ] . join ( '\n' ) ;
581622 }
582623 return [
583624 `import { hydrate } from '@solidjs/web';` ,
584625 ...( serverComponents
585626 ? [ `import { installServerComponents } from '@solidjs/web/frames';` ]
586627 : [ ] ) ,
628+ ...errorBoundaryImport ( ) ,
587629 `import Document from ${ JSON . stringify ( documentSpec ( ) ) } ;` ,
588630 `import App from ${ JSON . stringify ( app ) } ;` ,
589631 `` ,
@@ -597,9 +639,7 @@ export function startServe(
597639 ]
598640 : [ ] ) ,
599641 `hydrate(() => (` ,
600- ` <Document>` ,
601- ` <App />` ,
602- ` </Document>` ,
642+ ...documentTree ( 'App' ) ,
603643 `), document);` ,
604644 ] . join ( '\n' ) ;
605645 }
@@ -627,6 +667,29 @@ export function startServe(
627667 `}` ,
628668 ] . join ( '\n' ) ;
629669
670+ const errorBoundaryCode = [
671+ `import { Errored } from 'solid-js';` ,
672+ `import { httpStatus, isServer } from '@solidjs/web';` ,
673+ `` ,
674+ `function ErrorFallback(props) {` ,
675+ ` console.error(props.error());` ,
676+ ` httpStatus(500);` ,
677+ ` return (` ,
678+ ` <span style="font-size:1.5em;text-align:center;position:fixed;left:0;bottom:55%;width:100%">` ,
679+ ` {isServer ? '500 | Internal Server Error' : 'Error | Uncaught Client Exception'}` ,
680+ ` </span>` ,
681+ ` );` ,
682+ `}` ,
683+ `` ,
684+ `export function DefaultErrorBoundary(props) {` ,
685+ ` return (` ,
686+ ` <Errored fallback={(error) => <ErrorFallback error={error} />}>` ,
687+ ` {props.children}` ,
688+ ` </Errored>` ,
689+ ` );` ,
690+ `}` ,
691+ ] . join ( '\n' ) ;
692+
630693 // The handler module: dev and prod share the render/response plumbing;
631694 // they differ in how the client entry URL is known (baked dev URL vs a
632695 // manifest scan) and what gets injected into <head> (Vite client + style
@@ -1034,7 +1097,12 @@ export function startServe(
10341097 if ( source === DEV_STYLES_ID ) {
10351098 return { id : RESOLVED_DEV_STYLES_ID , moduleSideEffects : true } ;
10361099 }
1037- if ( source === ENTRY_SERVER_ID || source === ENTRY_CLIENT_ID || source === DOCUMENT_ID ) {
1100+ if (
1101+ source === ENTRY_SERVER_ID ||
1102+ source === ENTRY_CLIENT_ID ||
1103+ source === DOCUMENT_ID ||
1104+ source === ERROR_BOUNDARY_ID
1105+ ) {
10381106 return { id : source , moduleSideEffects : source === ENTRY_CLIENT_ID } ;
10391107 }
10401108 return null ;
@@ -1060,6 +1128,7 @@ export function startServe(
10601128 if ( id === ENTRY_SERVER_ID ) return generatedEntryServerCode ( ) ;
10611129 if ( id === ENTRY_CLIENT_ID ) return generatedEntryClientCode ( ) ;
10621130 if ( id === DOCUMENT_ID ) return documentShellCode ;
1131+ if ( id === ERROR_BOUNDARY_ID ) return errorBoundaryCode ;
10631132 return null ;
10641133 } ,
10651134 configurePreviewServer ( server : PreviewServer ) {
0 commit comments