Skip to content

Commit c16587c

Browse files
authored
Merge pull request #1961 from SUI-Components/initial-props-error-handling
feat(packages/sui-react-initial-props): add error handling and logging to getInitialProps
2 parents 892e3dc + 37cb3c4 commit c16587c

File tree

2 files changed

+91
-58
lines changed

2 files changed

+91
-58
lines changed

packages/sui-react-initial-props/README.md

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
11
# sui-react-initial-props
2+
23
> Make your React pages to get initial props asynchronously both client and server
34
45
## Motivation
56

67
**sui-react-initial-props** offers a way to make your easily your app isomorphic.
7-
* Offers same parameters for your getInitialProps in the client in the server to make your app 100% universal.
8-
* Avoid re-renders as other options like React-Transmit causing a longer time to respond, specially in the server.
9-
* Minimal footprint by focusing on the really need stuf.
8+
9+
- Offers same parameters for your getInitialProps in the client in the server to make your app 100% universal.
10+
- Avoid re-renders as other options like React-Transmit causing a longer time to respond, specially in the server.
11+
- Minimal footprint by focusing on the really need stuf.
1012

1113
![example]
1214

1315
## Usage
1416

1517
```js
1618
import loadPage from '@s-ui/react-initial-props/lib/loadPage'
17-
// contextFactory is not used anymore but the param is needed to be present for compatibility reasons
18-
const contextFactory = null
19+
20+
// Optional logger for error handling
21+
const logger = {
22+
error: (message, error) => {
23+
console.error(message, error)
24+
// Send to your logging service
25+
}
26+
}
1927

2028
// use the loadPage from the sui-react-initial-props
21-
const loadHomePage = loadPage(contextFactory,
22-
() => import(/* webpackChunkName: "HomePage" */ './pages/Home')
29+
const loadHomePage = loadPage(
30+
() => import(/* webpackChunkName: "HomePage" */ './pages/Home'),
31+
logger // optional: for server-side error logging
2332
)
2433

2534
export default (
@@ -101,7 +110,6 @@ Page.keepMounted = true
101110
Page.renderLoading = () => <h1>Loading...</h1>
102111
```
103112
104-
105113
## Installation
106114
107115
```sh
@@ -116,65 +124,83 @@ Create the params for the contextFactory on the client
116124
117125
##### Response
118126
119-
Field | Type | Description
120-
--- | --- | ---
121-
cookies | `string` | All the cookies of the user
122-
isClient | `boolean` | Useful to know in your contextFactory if you're in the client
123-
pathName | `string` | Current path of the url requested
124-
userAgent | `string` | Information of the browser, device and version in raw
127+
| Field | Type | Description |
128+
| --------- | --------- | ------------------------------------------------------------- |
129+
| cookies | `string` | All the cookies of the user |
130+
| isClient | `boolean` | Useful to know in your contextFactory if you're in the client |
131+
| pathName | `string` | Current path of the url requested |
132+
| userAgent | `string` | Information of the browser, device and version in raw |
125133
126134
#### createServerContextFactoryParams({ req })
127135
128136
Create the params for the contextFactory on the server
129137
130138
##### Params
131139
132-
Field | Type | Description
133-
--- | --- | ---
134-
req | `object` | [Native Node Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) with any customized property added on your middleware
140+
| Field | Type | Description |
141+
| ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
142+
| req | `object` | [Native Node Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) with any customized property added on your middleware |
135143
136144
##### Response
137145
138-
Field | Type | Description
139-
--- | --- | ---
140-
cookies | `string` | All the cookies of the user
141-
isClient | `boolean` | Useful to know in your contextFactory if you're in the client
142-
pathName | `string` | Current path of the url requested
143-
req | `object` | [Native Node Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) with any customized property added on your middleware
144-
userAgent | `string` | Information of the browser, device and version in raw
146+
| Field | Type | Description |
147+
| --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
148+
| cookies | `string` | All the cookies of the user |
149+
| isClient | `boolean` | Useful to know in your contextFactory if you're in the client |
150+
| pathName | `string` | Current path of the url requested |
151+
| req | `object` | [Native Node Incoming Message](https://nodejs.org/api/http.html#http_class_http_incomingmessage) with any customized property added on your middleware |
152+
| userAgent | `string` | Information of the browser, device and version in raw |
145153
146154
#### ssrComponentWithInitialProps({ Target, context, renderProps })
147155
148156
This method, retrieves the component page with the `getInitialProps` method, executes the async method and when it receives the info, then render to a string using the `Target` component and passing down the `context`.
149157
150158
##### Params
151159
152-
Field | Type | Description
153-
--- | --- | ---
154-
Target | `React Element` | React Element to be used for passing the context and render the app on it.
155-
context | `object` | Context to be passed to the Target component and to the `getInitialProps`
156-
renderProps | `object` | Props used by React Router with some useful info. We're extracting the pageComponent from it
160+
| Field | Type | Description |
161+
| ----------- | --------------- | -------------------------------------------------------------------------------------------- |
162+
| Target | `React Element` | React Element to be used for passing the context and render the app on it. |
163+
| context |  `object` | Context to be passed to the Target component and to the `getInitialProps` |
164+
| renderProps | `object` | Props used by React Router with some useful info. We're extracting the pageComponent from it |
157165
158166
##### Response
159167
160168
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:
161169
162-
Field | Type | Description
163-
--- | --- | ---
164-
initialProps | `object` | Result of executing the `getInitialProps` of the pageComponent.
165-
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)
166-
reactString | `string` | String with the renderized app ready to be sent.
170+
| Field | Type | Description |
171+
| --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
172+
| initialProps | `object` | Result of executing the `getInitialProps` of the pageComponent. |
173+
| 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) |
174+
| reactString | `string` | String with the renderized app ready to be sent. |
167175
168-
#### loadPage(contextFactory, importPage)
176+
#### loadPage(importPage, logger?)
169177
170-
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.
178+
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.
171179
172180
##### Params
173181
174-
Field | Type | Description
175-
--- | --- | ---
176-
contextFactory | `function` | Context factory method to create the context that will be used on the app.
177-
importPage | `function` | Import the chunk of the page
182+
| Field | Type | Description |
183+
| ---------- | ------------------- | ---------------------------------------------------------------------------------------------------------- |
184+
| importPage | `function` | Import the chunk of the page |
185+
| logger | `object` (optional) | Optional logger object with an `error(message: string, error: Error)` method for server-side error logging |
186+
187+
##### Error Handling
188+
189+
When `getInitialProps` throws an error on the server:
190+
191+
- The error is caught, logged using the provided `logger` (if available), and then **re-thrown**.
192+
- 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.
193+
194+
Example logger implementation:
195+
196+
```js
197+
const logger = {
198+
error: (message, error) => {
199+
console.error(message, error)
200+
// Send to Sentry, DataDog, etc.
201+
}
202+
}
203+
```
178204
179205
## Contributing
180206

packages/sui-react-initial-props/src/loadPage.tsx

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@
22
import {useContext} from 'react'
33

44
import InitialPropsContext from './initialPropsContext'
5-
import {
6-
type ClientPageComponent,
7-
type DoneImportingPageCallback,
8-
type ReactRouterTypes,
9-
type WithInitialPropsComponent
10-
} from './types'
5+
import {type ClientPageComponent, type ReactRouterTypes, type WithInitialPropsComponent} from './types'
116
import withInitialProps from './withInitialProps'
127

138
const EMPTY_GET_INITIAL_PROPS = async (): Promise<object> => ({})
149

10+
interface Logger {
11+
error: (message: string, error: Error) => void
12+
}
13+
1514
const createUniversalPage =
16-
(routeInfo: ReactRouterTypes.RouteInfo) =>
15+
(routeInfo: ReactRouterTypes.RouteInfo, logger?: Logger) =>
1716
async ({default: Page}: {default: ClientPageComponent}) => {
1817
// check if the Page page has a getInitialProps, if not put a resolve with an empty object
1918
Page.getInitialProps = typeof Page.getInitialProps === 'function' ? Page.getInitialProps : EMPTY_GET_INITIAL_PROPS
@@ -23,13 +22,15 @@ const createUniversalPage =
2322
// let withInitialProps HOC handle client getInitialProps logic
2423
return Promise.resolve(withInitialProps(Page))
2524
}
25+
2626
// SERVER
2727
// Create a component that gets the initialProps from context
2828
// this context has been created on the `ssrWithComponentWithInitialProps`
2929
const ServerPage: WithInitialPropsComponent = (props: object) => {
3030
const {initialProps} = useContext(InitialPropsContext)
3131
return <Page {...props} {...initialProps} />
3232
}
33+
3334
// recover the displayName from the original page
3435
ServerPage.displayName = Page.displayName
3536
// detect if the page has getInitialProps and wrap it with the routeInfo
@@ -38,18 +39,24 @@ const createUniversalPage =
3839
context: object,
3940
req: IncomingMessage.ServerRequest,
4041
res: IncomingMessage.ClientResponse
41-
) => await Page.getInitialProps({context, routeInfo, req, res})
42+
) => {
43+
try {
44+
return await Page.getInitialProps({context, routeInfo, req, res})
45+
} catch (error) {
46+
const message = 'Error executing getInitialProps on server'
47+
48+
logger?.error?.(message, error as Error)
49+
50+
throw error
51+
}
52+
}
53+
4254
// return the component to be used on the server
4355
return ServerPage
4456
}
4557

46-
// TODO: Remove this method on next major as it's using unnecessary contextFactory param
47-
// and unnecesary calling done method instead relying on promises
48-
export default (_: any, importPage: () => Promise<any>) =>
49-
async (routeInfo: ReactRouterTypes.RouteInfo, done: DoneImportingPageCallback) => {
50-
importPage()
51-
.then(createUniversalPage(routeInfo))
52-
.then(Page => {
53-
done(null, Page)
54-
})
55-
}
58+
export default (importPage: () => Promise<any>, logger?: Logger) => async (routeInfo: ReactRouterTypes.RouteInfo) => {
59+
await importPage()
60+
61+
return createUniversalPage(routeInfo, logger)
62+
}

0 commit comments

Comments
 (0)