Skip to content

Commit fbbde20

Browse files
committed
Document the document template option
1 parent 3bc5afc commit fbbde20

File tree

2 files changed

+94
-19
lines changed

2 files changed

+94
-19
lines changed

README.md

Lines changed: 90 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
![Build Status](https://github.com/gadget-inc/fastify-renderer/workflows/ci/badge.svg)
44
[![NPM version](https://img.shields.io/npm/v/fastify-renderer.svg?style=flat)](https://www.npmjs.com/package/fastify-renderer)
55

6-
`fastify-renderer` renders client side JavaScript applications on the server to improve the user experience.
6+
`fastify-renderer` renders client side JavaScript applications on the server to improve user experience. `fastify-renderer` is a standard Fastify plugin that mounts into an existing fastify application, so it's easy to use for only some routes.
77

8-
- It renders client side apps server side with minimal setup
9-
- Supports optional async work ahead of time using handy server side bits like a database connection or session stored in redis
10-
- Serves server side props to client side pages as the navigations happen client side
11-
- Control things outside the inner application like the `<head/>` tag when server rendering
8+
`fastify-renderer` works similarly to `next.js` and other SSR frameworks, where the first pageload a user makes is server side rendered, and then a client side app is booted. Subsequent navigations are handled client side, where the client makes a `fetch` call to retrieve the next set of `props` for the next page as JSON data, instead of re-rendering the whole page server side.
129

10+
Features:
11+
12+
- React SSR for fastify using `vite`
13+
- Allows async work ahead of time to produce props for the page, which means you can use a database connection, redis, or whatever else you have server side in Fastify
14+
- Support for rich React component layouts wrapping each page
15+
- Support for fastify plugin encapsulation contexts, so can be used to support different "areas" of an app within the same fastify server
16+
- Support for managing the full document shape using EJS templates
1317

1418
## Installation
1519

@@ -22,7 +26,6 @@ yarn install fastify-renderer
2226

2327
## Registering the plugin
2428

25-
2629
```js
2730
import fastify from 'fastify'
2831
import renderer from 'fastify-renderer'
@@ -31,7 +34,40 @@ const server = fastify()
3134
server.register(renderer)
3235
```
3336

37+
After registering the plugin, you need to set a render config for a Fastify encapsulation context.
38+
39+
```js
40+
// at the root, you can set the render config for the whole server
41+
server.setRenderConfig({
42+
layout: '../client/Layout.jsx',
43+
})
44+
45+
// or within a context, you can set the render config for just routes defined in that context
46+
server.register(async (server) => {
47+
server.setRenderConfig({
48+
base: '/auth',
49+
layout: '../client/auth/AuthLayout',
50+
})
51+
52+
server.get('/auth/login', { render: require.resolve('../client/auth/LoginPage') }, async (request) => ({}))
53+
})
54+
```
55+
56+
## Rendering from routes
57+
58+
Once the plugin is registered, any route in your Fastify application can render components. To make a route render a component, pass a component path as the `render` option to the route options:
59+
60+
```js
61+
server.get('/sample-route', { render: require.resolve('../client/ExampleComponent') }, async (_request) => {
62+
const props = { hello: 'world' }
63+
return props
64+
})
65+
```
66+
67+
Unlike a normal Fastify route handler, render route functions don't use `reply.send` to return HTML content. Instead, your route handler function should return a `props` object. This `props` object will be passed to the rendered component when being rendered server side, and will be fetched from this route and passed to the component when being rendered client side.
68+
3469
### Configuration options
70+
3571
You can optionally provide configuration options to the plugin:
3672

3773
- `renderer` - Object that provides the rendering options
@@ -40,30 +76,65 @@ You can optionally provide configuration options to the plugin:
4076
- `vite` - Vite [InlineConfig](https://vitejs.dev/guide/api-javascript.html#inlineconfig) options
4177
- `base` - The base path we want our renderer to use
4278
- `layout` - The path to a layout component inside of which other routes will be rendered
43-
- `document` - HTML template inside of which everything is rendered
79+
- `document` - HTML template inside of which everything is rendered. The `template` option accepts a function which should return a `ReadableStream` to produce the output HTML.
4480
- `devMode` - Boolean, when true a vite devServer is created
4581
- `outDir` - The directory where the files generated by the Vite build will be created
4682
- `assetsHost` - The host url from which files will be accessible to the browser
47-
- `hooks` - Array of FastifyRendererHook
48-
- `name` - Optional string value
83+
- `hooks` - Array of FastifyRendererHook
84+
- `name` - Optional string value
4985
- `heads` - Function that will return html tags to be appended to the document head tag
5086
- `tails` - Function that will return html tags to be appended to the document body tag
5187
- `transform` - Function that will be run to transform the root react element
5288
- `postRenderHeads` - Function (called after render) that will return html tags to be appended to the document head tag. Useful when injecting styles that rely on rendering first.
5389

90+
The plugin will render the component server side and return it, where as the route handler will return the props to the frontend when needed.
5491

55-
## Setting up a route
56-
To set up a route, you can do the following:
57-
```js
58-
server.get('/sample-route', { render: require.resolve('./Component') }, async (_request) => {
59-
const props = { hello: 'world' }
60-
return props
61-
})
92+
#### Controlling the document template
93+
94+
The raw HTML wrapper for each SSR page can be controlled with the `document` option passed at the root level plugin config or with `.setRenderConfig`. The `document` option accepts a function which is given render data, and should return a `ReadableStream` object for piping to the browser. We use a streaming interface in order to stream the result efficiently to the browser before the whole render is complete.
95+
96+
The function is passed a `data` object which should be used for interpolating values into your output stream. Here's the shape of the `data` object:
97+
98+
```typescript
99+
/** Data passed to the template function by the renderer */
100+
interface TemplateData<Props> {
101+
/** The content for including in the `<head/>` tag of the rendered document */
102+
head: NodeJS.ReadableStream
103+
/** The content for including after the app just before the `</body>` tag of the rendered document */
104+
tail: NodeJS.ReadableStream
105+
/** The main content for the app */
106+
content: string | NodeJS.ReadableStream
107+
/** The props object generated by the route handler for this render */
108+
props: Props
109+
}
62110
```
63-
The plugin will render the component server side and return it, where as the route handler will return the props to the frontend when needed.
64111

112+
Usually, the `stream-template` package is used for this for super easy interpolation of streams.
113+
114+
Here's the default template source:
115+
116+
```typescript
117+
import template from 'stream-template'
118+
119+
export const DefaultDocumentTemplate: Template = (data: TemplateData<any>) => template`
120+
<!DOCTYPE html>
121+
<html lang="en">
122+
<head>
123+
<meta charset="UTF-8" />
124+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
125+
<title>${data.props.title || 'Fastify Renderer App'}</title>
126+
${data.head}
127+
</head>
128+
<body>
129+
<div id="fstrapp">${data.content}</div>
130+
${data.tail}
131+
</body>
132+
</html>
133+
`
134+
```
65135

66136
## How it works
137+
67138
- mounts a `vite` server as a fastify plugin that knows how to transform code to be run server side.
68139
- provides renderers that use vite for react and other popular frameworks (Currently only React is supported)
69140
- provides a convention for async ahead of time work to pass data into renderers
@@ -76,7 +147,7 @@ The goal of fastify-renderer is to bring a great developer/user experience and i
76147
- next.js has a great developer experience but blocks the main thread when rendering react.
77148
- next.js uses express and webpack underneath, both of which have much better performing alternatives
78149
- next.js must be both the bundler and the server, it's hard to mount it into an existing system like fastify and play nice with all the other plugins you might want to use when doing :gasp: server side rendered :gasp: applications
79-
- vite is awesome but also wants to be the server and keep the frontend entirely seperate from the backend
150+
- vite is awesome but also wants to be the server and keep the frontend entirely separate from the backend
80151
- server side rendering is hard and reinventing that wheel is no fun
81152
- hot reloading in development is hard and reinventing that wheel is no fun
82153
- bundling for production is hard and reinventing that wheel is no fun
@@ -86,8 +157,8 @@ The goal of fastify-renderer is to bring a great developer/user experience and i
86157

87158
- [Navigating between routes from different Fastify contexts](./docs/fastify-contexts)
88159

89-
90160
## Roadmap
161+
91162
- Add support for rendering outside the main thread by using Piscina.js
92163
- Add support for Vue and other frontend frameworks
93164

packages/fastify-renderer/src/node/DocumentTemplate.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ import template from 'stream-template'
22

33
/** Data passed to the template function by the renderer */
44
export interface TemplateData<Props> {
5+
/** The content for including in the `<head/>` tag of the rendered document */
56
head: NodeJS.ReadableStream
7+
/** The content for including after the app just before the `</body>` tag of the rendered document */
68
tail: NodeJS.ReadableStream
9+
/** The main content for the app */
710
content: string | NodeJS.ReadableStream
11+
/** The props object generated by the route handler for this render */
812
props: Props
913
}
1014

0 commit comments

Comments
 (0)