@react-router/serve

React Router App Server

React Router is designed for you to own your server, but if you don't want to set one up, you can use the React Router App Server instead. It's a production-ready but basic Node.js server built with Express.

By design, we do not provide options to customize the React Router App Server because if you need to customize the underlying express server, we'd rather you manage the server completely instead of creating an abstraction to handle all the possible customizations you may require. If you find you want to customize it, you can migrate to the @react-router/express adapter.

You can see the underlying express server configuration in packages/react-router-serve/cli.ts. By default, it uses the following Express middlewares (please refer to their documentation for default behaviors):

HOST environment variable

You can configure the hostname for your Express app via process.env.HOST and that value will be passed to the internal app.listen method when starting the server.

HOST=127.0.0.1 npx react-router-serve build/index.js
react-router-serve <server-build-path>
# e.g.
react-router-serve build/index.js

PORT environment variable

You can change the port of the server with an environment variable.

PORT=4000 npx react-router-serve build/index.js

Development Environment

Depending on process.env.NODE_ENV, the server will boot in development or production mode.

The server-build-path needs to point to the serverBuildPath defined in react-router.config.ts.

Because only the build artifacts (build/, public/build/) need to be deployed to production, the react-router.config.ts is not guaranteed to be available in production, so you need to tell React Router where your server build is with this option.

In development, react-router-serve will ensure the latest code is run by purging the require cache for every request. This has some effects on your code you might need to be aware of:

  • Any values in the module scope will be "reset"

    // this will be reset for every request because the module cache was
    // cleared and this will be required brand new
    const cache = new Map();
    
    export async function loader({ params }) {
      if (cache.has(params.foo)) {
        return cache.get(params.foo);
      }
    
      const record = await fakeDb.stuff.find(params.foo);
      cache.set(params.foo, record);
      return record;
    }
    
    If you need a workaround for preserving cache in development, you can set up a [singleton][singleton] in your server.
    
    
  • Any module side effects will remain in place! This may cause problems but should probably be avoided anyway.

    // this starts running the moment the module is imported
    setInterval(() => {
      console.log(Date.now());
    }, 1000);
    
    export async function loader() {
      // ...
    }
    

    If you need to write your code in a way that has these types of module side effects, you should set up your own @react-router/express server and a tool in development like pm2-dev or nodemon to restart the server on file changes instead.

In production this doesn't happen. The server boots up, and that's the end of it.

Docs and examples CC 4.0
Edit