Boilerplate for React apps with routing, code splitting, & server side rendering
Tools like create-react-app have made setting up client-side React apps trivial, but transitioning to SSR is still kind of a pain in the ass. Next.js is a powerhouse, and the Razzle tool looks like an absolute beast, but sometimes you just want to see the whole enchilada running your app. This is a sample setup for fully featured, server-rendered React applications.
What's included:
<head />
npm install
npm start
npm test
jest
in watch modenpm run build && npm run start:prod
npm run docker
This app has two main pieces: the server and the client code.
server/
)A fairly basic Express application in server/app.js
handles serving static assets (the generated CSS and JS code in build/
+ anything in public/
like images and fonts), and sends all other requests to the React application via server/renderServerSideApp.js
. That function delegates the fetching of server-side data fetching to server/fetchDataForRender
, and then sends the rendered React application (as a string) injected inside the HTML-ish code in server/indexHtml.js
.
During development the server code is run with @babel/register
and middleware is added to the Express app (see scripts/start
), and in production we bundle the server code to build/server
and the code in scripts/startProd
is used to run the server with Node's cluster
module to take advantage of multiple CPU cores.
src/
)The entrypoint for the client-side code (src/index.js
) first checks if the current browser needs to be polyfilled and then defers to src/main.js
to hydrate the React application. These two files are only ever called on the client, so you can safely reference any browser APIs here without anything fancy. The rest of the client code is a React application -- in this case a super basic UI w/2 routes, but you can safely modify/delete nearly everything inside src/
and make it your own.
As with all server-rendered React apps you'll want to be cautious of using browser APIs in your components -- they don't exist when rendering on the server and will throw errors unless you handle them gracefully (I've found some success with using if (typeof myBrowserAPI !== 'undefined') { ... }
checks when necessary, but it feels dirty so I try to avoid when possible). The one exception to this is the componentDidMount()
method for class components and useEffect()
& useLayoutEffect()
hooks, which are only run on the client.
The client-side sample code to handle is a little experimental at the moment.
Sometimes you'll want to make API calls on the server to fetch data before rendering the page. In those cases you can use a static fetchData()
method on any component. That method will be called with the req
object from express, and it should return a Promise that resolves to an object, which will be merged with other fetchData()
return values into a single object. That object of server data is injected into the server HTML, added to window.__SERVER_DATA__
, and used to hydrate the client via the <ServerDataProvider />
context provider. Components can use the useServerData()
hook to grab the data object. IMPORTANT: Your component must handle the case where the server data property it's reading from is undefined
.
Check out src/components/Home.js
for an example.
Adding redux
takes a few steps, but shouldn't be too painful; start by replacing the <ServerDataProvider />
with the <Provider />
from react-redux
on both the server and the client. You can then pass the store
as an argument to the static fetchData()
method (in server/fetchDataForRender.js
) and dispatch actions inside of fetchData()
. Finally you'll need to pass the store
's current state to the index.html generator function so you can grab it on the client and hydrate the client-side store
.
url-loader
or file-loader
(so no import src from 'my-img.svg'
). Instead it relies on serving static assets via the public/
directory. See src/components/about/About.js
for a reference on how to work with assets in your app..module.s?css
file extensionreact-testing-library
instead of enzyme