From 9e9ac26b5c0f9c17d2404bcca379d095c9b23136 Mon Sep 17 00:00:00 2001 From: Kiko Ruiz Date: Wed, 26 Nov 2025 12:11:11 +0100 Subject: [PATCH 1/5] feat(packages/sui-react-initial-props): add error handling and logging to getInitialProps --- .../sui-react-initial-props/src/loadPage.tsx | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/sui-react-initial-props/src/loadPage.tsx b/packages/sui-react-initial-props/src/loadPage.tsx index 887335b08..15c153150 100644 --- a/packages/sui-react-initial-props/src/loadPage.tsx +++ b/packages/sui-react-initial-props/src/loadPage.tsx @@ -2,18 +2,17 @@ import {useContext} from 'react' import InitialPropsContext from './initialPropsContext' -import { - type ClientPageComponent, - type DoneImportingPageCallback, - type ReactRouterTypes, - type WithInitialPropsComponent -} from './types' +import {type ClientPageComponent, type ReactRouterTypes, type WithInitialPropsComponent} from './types' import withInitialProps from './withInitialProps' const EMPTY_GET_INITIAL_PROPS = async (): Promise => ({}) +interface Logger { + error: (message: string, error: Error) => void +} + const createUniversalPage = - (routeInfo: ReactRouterTypes.RouteInfo) => + (routeInfo: ReactRouterTypes.RouteInfo, logger?: Logger) => async ({default: Page}: {default: ClientPageComponent}) => { // check if the Page page has a getInitialProps, if not put a resolve with an empty object Page.getInitialProps = typeof Page.getInitialProps === 'function' ? Page.getInitialProps : EMPTY_GET_INITIAL_PROPS @@ -38,18 +37,22 @@ const createUniversalPage = context: object, req: IncomingMessage.ServerRequest, res: IncomingMessage.ClientResponse - ) => await Page.getInitialProps({context, routeInfo, req, res}) + ) => { + try { + return await Page.getInitialProps({context, routeInfo, req, res}) + } catch (error) { + logger?.error?.('Error executing getInitialProps on server', error as Error) + + return {} + } + } + // return the component to be used on the server return ServerPage } -// TODO: Remove this method on next major as it's using unnecessary contextFactory param -// and unnecesary calling done method instead relying on promises -export default (_: any, importPage: () => Promise) => - async (routeInfo: ReactRouterTypes.RouteInfo, done: DoneImportingPageCallback) => { - importPage() - .then(createUniversalPage(routeInfo)) - .then(Page => { - done(null, Page) - }) - } +export default (importPage: () => Promise, logger?: Logger) => async (routeInfo: ReactRouterTypes.RouteInfo) => { + await importPage() + + return createUniversalPage(routeInfo, logger) +} From 6458c459fa3315df3ccf09114a5c1f12ef923dbe Mon Sep 17 00:00:00 2001 From: Kiko Ruiz Date: Wed, 26 Nov 2025 12:20:17 +0100 Subject: [PATCH 2/5] feat(packages/sui-react-initial-props): send message to the component --- packages/sui-react-initial-props/src/loadPage.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/sui-react-initial-props/src/loadPage.tsx b/packages/sui-react-initial-props/src/loadPage.tsx index 15c153150..110d28026 100644 --- a/packages/sui-react-initial-props/src/loadPage.tsx +++ b/packages/sui-react-initial-props/src/loadPage.tsx @@ -22,6 +22,7 @@ const createUniversalPage = // let withInitialProps HOC handle client getInitialProps logic return Promise.resolve(withInitialProps(Page)) } + // SERVER // Create a component that gets the initialProps from context // this context has been created on the `ssrWithComponentWithInitialProps` @@ -29,6 +30,7 @@ const createUniversalPage = const {initialProps} = useContext(InitialPropsContext) return } + // recover the displayName from the original page ServerPage.displayName = Page.displayName // detect if the page has getInitialProps and wrap it with the routeInfo @@ -41,9 +43,11 @@ const createUniversalPage = try { return await Page.getInitialProps({context, routeInfo, req, res}) } catch (error) { - logger?.error?.('Error executing getInitialProps on server', error as Error) + const message = 'Error executing getInitialProps on server' + + logger?.error?.(message, error as Error) - return {} + return {error: error instanceof Error ? error.message : message} } } From e64dda5a278fadcce8e80ad6d79f2bb525c40575 Mon Sep 17 00:00:00 2001 From: Kiko Ruiz Date: Wed, 26 Nov 2025 12:37:05 +0100 Subject: [PATCH 3/5] feat(packages/sui-react-initial-props): update docs BREAKING CHANGES: load page not receiving contextfactory anymore --- packages/sui-react-initial-props/README.md | 107 +++++++++++++-------- 1 file changed, 67 insertions(+), 40 deletions(-) diff --git a/packages/sui-react-initial-props/README.md b/packages/sui-react-initial-props/README.md index 9acae082e..3a11ad8b2 100644 --- a/packages/sui-react-initial-props/README.md +++ b/packages/sui-react-initial-props/README.md @@ -1,12 +1,14 @@ # sui-react-initial-props + > Make your React pages to get initial props asynchronously both client and server ## Motivation **sui-react-initial-props** offers a way to make your easily your app isomorphic. -* Offers same parameters for your getInitialProps in the client in the server to make your app 100% universal. -* Avoid re-renders as other options like React-Transmit causing a longer time to respond, specially in the server. -* Minimal footprint by focusing on the really need stuf. + +- Offers same parameters for your getInitialProps in the client in the server to make your app 100% universal. +- Avoid re-renders as other options like React-Transmit causing a longer time to respond, specially in the server. +- Minimal footprint by focusing on the really need stuf. ![example] @@ -14,12 +16,19 @@ ```js import loadPage from '@s-ui/react-initial-props/lib/loadPage' -// contextFactory is not used anymore but the param is needed to be present for compatibility reasons -const contextFactory = null + +// Optional logger for error handling +const logger = { + error: (message, error) => { + console.error(message, error) + // Send to your logging service + } +} // use the loadPage from the sui-react-initial-props -const loadHomePage = loadPage(contextFactory, - () => import(/* webpackChunkName: "HomePage" */ './pages/Home') +const loadHomePage = loadPage( + () => import(/* webpackChunkName: "HomePage" */ './pages/Home'), + logger // optional: for server-side error logging ) export default ( @@ -101,7 +110,6 @@ Page.keepMounted = true Page.renderLoading = () =>

Loading...

``` - ## Installation ```sh @@ -116,12 +124,12 @@ Create the params for the contextFactory on the client ##### Response -Field | Type | Description ---- | --- | --- -cookies | `string` | All the cookies of the user -isClient | `boolean` | Useful to know in your contextFactory if you're in the client -pathName | `string` | Current path of the url requested -userAgent | `string` | Information of the browser, device and version in raw +| Field | Type | Description | +| --------- | --------- | ------------------------------------------------------------- | +| cookies | `string` | All the cookies of the user | +| isClient | `boolean` | Useful to know in your contextFactory if you're in the client | +| pathName | `string` | Current path of the url requested | +| userAgent | `string` | Information of the browser, device and version in raw | #### createServerContextFactoryParams({ req }) @@ -129,19 +137,19 @@ Create the params for the contextFactory on the server ##### Params -Field | Type | Description ---- | --- | --- -req | `object` | [Native Node Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) with any customized property added on your middleware +| Field | Type | Description | +| ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| req | `object` | [Native Node Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) with any customized property added on your middleware | ##### Response -Field | Type | Description ---- | --- | --- -cookies | `string` | All the cookies of the user -isClient | `boolean` | Useful to know in your contextFactory if you're in the client -pathName | `string` | Current path of the url requested -req | `object` | [Native Node Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) with any customized property added on your middleware -userAgent | `string` | Information of the browser, device and version in raw +| Field | Type | Description | +| --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| cookies | `string` | All the cookies of the user | +| isClient | `boolean` | Useful to know in your contextFactory if you're in the client | +| pathName | `string` | Current path of the url requested | +| req | `object` | [Native Node Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) with any customized property added on your middleware | +| userAgent | `string` | Information of the browser, device and version in raw | #### ssrComponentWithInitialProps({ Target, context, renderProps }) @@ -149,32 +157,51 @@ This method, retrieves the component page with the `getInitialProps` method, exe ##### Params -Field | Type | Description ---- | --- | --- -Target | `React Element` | React Element to be used for passing the context and render the app on it. -context | `object` | Context to be passed to the Target component and to the `getInitialProps` -renderProps | `object` | Props used by React Router with some useful info. We're extracting the pageComponent from it +| Field | Type | Description | +| ----------- | --------------- | -------------------------------------------------------------------------------------------- | +| Target | `React Element` | React Element to be used for passing the context and render the app on it. | +| context |  `object` | Context to be passed to the Target component and to the `getInitialProps` | +| renderProps | `object` | Props used by React Router with some useful info. We're extracting the pageComponent from it | ##### Response The response is a promise resolved with two parameters. In addition, you can define an optional `__HTTP__` object in `initialProps` to allow server side redirects using SUI-SSR: -Field | Type | Description ---- | --- | --- -initialProps | `object` | Result of executing the `getInitialProps` of the pageComponent. -initialprops.__HTTP__ | `object` | An optional object containing a `redirectTo` key where an url might be included to allow 3XX server side redirects using [sui-ssr]. By default, redirect status code is 301, but you may set a valid `redirectStatusCode` option set in the file `@s-ui/ssr/status-codes`, an optional `httpCookie` key where you will define an object with the key/value of the `Http-Cookie` to be set from server and an optional `headers` key array of objects where you will define a custom response headers (see https://github.com/SUI-Components/sui/tree/master/packages/sui-ssr) -reactString | `string` | String with the renderized app ready to be sent. +| Field | Type | Description | +| --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| initialProps | `object` | Result of executing the `getInitialProps` of the pageComponent. | +| initialprops.**HTTP** | `object` | An optional object containing a `redirectTo` key where an url might be included to allow 3XX server side redirects using [sui-ssr]. By default, redirect status code is 301, but you may set a valid `redirectStatusCode` option set in the file `@s-ui/ssr/status-codes`, an optional `httpCookie` key where you will define an object with the key/value of the `Http-Cookie` to be set from server and an optional `headers` key array of objects where you will define a custom response headers (see https://github.com/SUI-Components/sui/tree/master/packages/sui-ssr) | +| reactString | `string` | String with the renderized app ready to be sent. | -#### loadPage(contextFactory, importPage) +#### loadPage(importPage, logger?) -Load the page asynchronously by using React Router and resolving the getInitialProps. On the client it prepare the component to show the `renderLoading` (if specified) of the component. +Load the page asynchronously by using React Router and resolving the getInitialProps. On the client it prepare the component to show the `renderLoading` (if specified) of the component. On the server, it wraps `getInitialProps` execution in a try-catch block to prevent crashes. ##### Params -Field | Type | Description ---- | --- | --- -contextFactory | `function` | Context factory method to create the context that will be used on the app. -importPage | `function` | Import the chunk of the page +| Field | Type | Description | +| ---------- | ------------------- | ---------------------------------------------------------------------------------------------------------- | +| importPage | `function` | Import the chunk of the page | +| logger | `object` (optional) | Optional logger object with an `error(message: string, error: Error)` method for server-side error logging | + +##### Error Handling + +When `getInitialProps` throws an error on the server: + +- The error is caught and logged using the provided `logger` (if available) +- An empty object `{}` is returned instead of propagating the error +- This prevents the SSR process from crashing + +Example logger implementation: + +```js +const logger = { + error: (message, error) => { + console.error(message, error) + // Send to Sentry, DataDog, etc. + } +} +``` ## Contributing From 9fa482436958be11f5eca2dde7c3712657308455 Mon Sep 17 00:00:00 2001 From: Kiko Ruiz Date: Wed, 26 Nov 2025 12:39:43 +0100 Subject: [PATCH 4/5] docs(packages/sui-react-initial-props): fix doc --- packages/sui-react-initial-props/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sui-react-initial-props/README.md b/packages/sui-react-initial-props/README.md index 3a11ad8b2..94c6e28a8 100644 --- a/packages/sui-react-initial-props/README.md +++ b/packages/sui-react-initial-props/README.md @@ -189,7 +189,7 @@ Load the page asynchronously by using React Router and resolving the getInitialP When `getInitialProps` throws an error on the server: - The error is caught and logged using the provided `logger` (if available) -- An empty object `{}` is returned instead of propagating the error +- An object `{error: 'Error loading the page'}` with the error message is returned instead of propagating the error - This prevents the SSR process from crashing Example logger implementation: From 37cb3c4d617ad235e7ce1b21c9e069219a7640fc Mon Sep 17 00:00:00 2001 From: Kiko Ruiz Date: Fri, 5 Dec 2025 09:14:26 +0100 Subject: [PATCH 5/5] feat(packages/sui-react-initial-props): rethrow error on catch --- packages/sui-react-initial-props/README.md | 5 ++--- packages/sui-react-initial-props/src/loadPage.tsx | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/sui-react-initial-props/README.md b/packages/sui-react-initial-props/README.md index 94c6e28a8..139d0fee5 100644 --- a/packages/sui-react-initial-props/README.md +++ b/packages/sui-react-initial-props/README.md @@ -188,9 +188,8 @@ Load the page asynchronously by using React Router and resolving the getInitialP When `getInitialProps` throws an error on the server: -- The error is caught and logged using the provided `logger` (if available) -- An object `{error: 'Error loading the page'}` with the error message is returned instead of propagating the error -- This prevents the SSR process from crashing +- The error is caught, logged using the provided `logger` (if available), and then **re-thrown**. +- This allows the server's global error handling middleware to catch the exception and manage the response accordingly (e.g., render a 500 page), preventing the SSR process from crashing silently. Example logger implementation: diff --git a/packages/sui-react-initial-props/src/loadPage.tsx b/packages/sui-react-initial-props/src/loadPage.tsx index 110d28026..8c74b47d1 100644 --- a/packages/sui-react-initial-props/src/loadPage.tsx +++ b/packages/sui-react-initial-props/src/loadPage.tsx @@ -47,7 +47,7 @@ const createUniversalPage = logger?.error?.(message, error as Error) - return {error: error instanceof Error ? error.message : message} + throw error } }