A toolkit to facilitate setting up SSR in a frontend project.
- Add dependencies:
yarn add plume-ssr-server
yarn add -D express @types/express esbuild esbuild-runner
- Create a
building
folder to putdi-transformer-adapter.ts
andssr-compiler.ts
- Modify
tsconfig.json
to change:- the
di-transformer-adapter.ts
path:{"transform": "./building/di-transformer-adapter.ts" },
- Add
"server-ssr/index.ts",
in theinclude
section
- the
- Create source folder for ssr server:
mkdir server-ssr
- Change eslint ts config: add include
"server-ssr"
- Create the SSR module:
server-module.ts
- Register the config module:
injector.registerSingleton(ConfigProvider, SsrConfigProvider);
- Register the config module:
- Optionally, if the SSR module will need to read specific file configuration to start, create a folder
config
and a fileConfig.ts
- Modify
scripts
in thepackage.json
file:- Add
"run-ssr": "ttsc --incremental && yarn build-server && node build/server/index.js",
- Add
"start-server": "concurrently \"yarn watch-ts\" \"yarn run-ssr\"",
- Delete
"build": "rm -rf build && yarn build-client && yarn build-server",
- Add
"build-client": "vite build --outDir build/client",
- Add
"build-server": "esr building/ssr-compiler.ts",
- Add
"build": "rm -rf build && ttsc && yarn build-client && run build-server",
- Add
- Create a
conf-ssr.json
file - Update
vite.config.ts
config to addviteInlineCss
plugin - Change
index.html
file, import module/ts-built/src/index.js
instead of/ts-built/index.js
- Add rule in
.eslintrc.js
in therules
node :
'import/no-restricted-paths':[2,
{ "zones": [
// Files from './server-ssr' must never be accessed in './src' to avoid having node code running in the browser
{"target": "./src", "from": "./server-ssr"},
]},
]
- To handle correctly the router redirections during SSR :
- Use the
SsrLocationContextProvider
from theplume-ssr-browser
lib to wrap your application, And use amemoryRouter
in yourApplicationHtmlRenderer
:
const appRenderer: ApplicationHtmlRenderer<RenderedApplication> = (request: express.Request) => { const context: SsrLocationContext = {}; const appHtml = ReactDOMServer.renderToString( <SsrLocationContextProvider context={context}> <App createRouter={(routes) => createMemoryRouter(routes, { initialEntries: [request.originalUrl], initialIndex: 0, })} /> </SsrLocationContextProvider>, ); return { appHtml, redirectUrl: context.url ?? '' }; };
- Use the
Redirect
component from theplume-ssr-browser
lib instead ofNavigate
fromreact-router
. This component updates theSsrLocationContext
when a redirection is triggered.
- Use the
- Add the following eslint rules :
'no-restricted-imports': ['error', {
'paths': [
{
'name': 'react-router-dom',
'importNames': ['Navigate', 'useNavigate', 'redirect'],
'message': 'Please use the plume-ssr-browser version instead.',
},
{
name: 'micro-observables',
importNames: ['useObservable'],
message: 'Please use the plume-ssr-browser version instead.'
}
],
}],
TODO when #4 is resolved, add the missing rule
@TODO
Wrapper providing the location context to the SSR application.
When using React Router v6, it allows to reproduce the context provided by the {@link StaticRouter} of React Router v5, a feature that no longer exists in React Router v6.
SsrLocationContextProvider is primarily used to detect redirects during SSR rendering of the application by updating the {@link SsrLocationContext} when a redirect is triggered.
When a redirect is detected, a 304 response containing the new URL should be sent to the user's browser.
Observable data for SSR enables to:
- Serve cached page in SSR without having to make further API calls for each incoming request
- Parametrize this cache, for example by lang, by site, by page, etc.
When defining observable data, it is very important to correctly identity all the parameters that may change the data value. For example:
- Configuration: Does it depend on the current site (i.e. hostname)? The lang?
- PageData: Does it depend on the current site? The lang? The current page id? Some kind of category?
TODO details more
- Configure the project global
SsrObservableManager
- Defines observable in the project using
SsrWritableObservable
- Set observable values using
SsrObservableContent
- Use the
SsrWritableObservable.data()
method to access the observable data - Use the hook
useSsrObservableLoader
to access data in components in conjunction with the loading componentWithLoadingData
- Configure the SSR observable manager using
SsrServerObservableManager
Examples in the sample project: SsrBrowserObservableManager
, SampleService
, Home.tsx
, ShowSample.tsx
, ServerSsrObservableManager.ts
TODO debug config: see #3
TODO show an example with how the hostname configuration should be used
Yarn workspaces is used. This means that:
- To start working first
yarn install
andyarn build
must be run - To use any command on a specific workspace, the
yarn workspace
command must be used (from the root directory). For example:yarn workspace plume-ssr-browser build
oryarn workspace plume-ssr-browser add -D react
- The sample project should be used to test changes. So when some changes happen,
yarn build
oryarn workspace plume-ssr-server build
(with the correct project name) must be executed to build the library project
- Check:
git pull
- Check:
git status
- Build:
yarn prepare-release
- Prepare version:
yarn versions [version to release]
- Publish version on NPM:
yarn workspaces foreach --from 'plume-ssr-**' npm publish --access public --tag [version to release] --otp [otp code]
- Create git tag:
git tag [version to release]
- Push tag:
git push --tags
- Prepare next version:
yarn versions [next version]
- Commit & push:
git add . && git commit -m "Prepare new version" && git push