Edgio
Edgio

React

This guide shows you how to serve a React application on Layer0. If you’re using Next.js specifically, we suggest using the Next.js guide.

Example

Here’s an example React app running on Layer0:

System Requirements

Getting Started

To prepare your React app for deployment on Layer0, install the Layer0 CLI globally:

Terminal
npm i -g @layer0/cli # yarn global add @layer0/cli

New project

This guide will use Create React App to generate a project. You can also reference the example app for a complete version of the code.

Terminal
npx create-react-app layer0-cra
cd layer0-cra
0 init
# Pick the following options for questions
# > Add Layer0 to the current app
# Hostname of origin site > layer0-docs-layer0-examples-api-default.layer0-limelight.link

Follow the additional sections below regarding the Create React App setup to finish the project setup.

Existing project

Then, in the root folder of your project, run:

Terminal
0 init

This will automatically add all of the required dependencies and files to your project. These include:

  • The @layer0/core package - Allows you to declare routes and deploy your application on Layer0.
  • The @layer0/prefetch package - Allows you to configure a service worker to prefetch and cache pages to improve browsing speed.
  • layer0.config.js - The main configuration file for Layer0.
  • routes.js - A default routes file that sends all requests to React. This file can be updated add caching or proxy URLs to a different origin.

Configure your project

Layer0 Router

Using the Router class from @layer0/core, you’ll configure caching for each of your routes, and forward requests to the server module you configured in the previous section using the proxy function.

Note: Change dist to match whatever your configured output file is.

General routes file for React app

JavaScript
// routes.js

import { Router } from '@layer0/core/router'
import { BACKENDS } from '@layer0/core'

new Router()
  .get('/service-worker.js', ({ serviceWorker }) => {
    serviceWorker('dist/service-worker.js')
  })
  .get('/p/:id', ({ cache }) => {
    // cache product pages at the edge for 1 day
    cache({
      edge: {
        maxAgeSeconds: 60 * 60 * 24, // 1 day
      },
    })
  })
  .fallback(({ renderWithApp }) => {
    // send all requests to the server module configured in layer0.config.js
    renderWithApp()
  })

Create React App Example

After following the instructions from above, update your routes.js file to match this.

JavaScript
// routes.js

const { Router } = require('@layer0/core/router')

const ONE_HOUR = 60 * 60
const ONE_DAY = 24 * ONE_HOUR
const ONE_YEAR = 365 * ONE_DAY

const edgeOnly = {
  browser: false,
  edge: { maxAgeSeconds: ONE_YEAR },
}

const edgeAndBrowser = {
  browser: { maxAgeSeconds: ONE_YEAR },
  edge: { maxAgeSeconds: ONE_YEAR },
}

export default new Router()
  .prerender([{ path: '/' }])
  .match('/api/:path*', ({ cache, proxy }) => {
    cache(edgeAndBrowser)
    proxy('origin')
  })
  .match('/images/:path*', ({ cache, proxy }) => {
    cache(edgeAndBrowser)
    proxy('origin')
  })
  .match('/service-worker.js', ({ serviceWorker }) => serviceWorker('build/service-worker.js'))
  // match routes for js/css resources and serve the static files
  .match('/static/:path*', ({ serveStatic, cache }) => {
    cache(edgeAndBrowser)
    serveStatic('build/static/:path*')
  })
  // match client-side routes that aren't a static asset
  // and serve the app shell. client-side router will
  // handle the route once it is rendered
  .match('/:path*/:file([^\\.]+|)', ({ appShell, cache }) => {
    cache(edgeOnly)
    appShell('build/index.html')
  })
  // match other assets such as favicon, manifest.json, etc
  .match('/:path*', ({ serveStatic, cache }) => {
    cache(edgeOnly)
    serveStatic('build/:path*')
  })
  // send any unmatched request to origin
  .fallback(({ serveStatic }) => serveStatic('build/index.html'))

Prefetching

Install the @layer0/react to enable this feature.

Terminal
npm i -D @layer0/react

Add the Prefetch component from @layer0/react to your links to cache pages before the user clicks on them. Here’s an example:

JavaScript
import { Link } from 'react-router'
import { Prefetch } from '@layer0/react'

export default function ProductListing() {
  return (
    <div>
      {/* ... */}
      {/* The URL you need to prefetch is the API call that the page component will make when it mounts. It will vary based on how you've implemented your site. */}
      <Prefetch url="/api/products/1.json">
        <Link to="/p/1">Product 1</Link>
      </Prefetch>
      {/* ... */}
    </div>
  )
}

By default, Prefetch waits until the link appears in the viewport before prefetching. You can prefetch immediately by setting the immediately prop:

JavaScript
<Prefetch url="/api/products/1.json" immediately>
  <Link to="/p/1">Product 1</Link>
</Prefetch>

Service Worker

In order for prefetching to work, you need to configure a service worker that uses the Prefetcher class from @layer0/prefetch.

Following the Create React App example from above? Make sure to create a file in src/service-worker.js. Paste the code example below into that file.

Here is an example service worker:

JavaScript
import { skipWaiting, clientsClaim } from 'workbox-core'
import { precacheAndRoute } from 'workbox-precaching'
import DeepFetchPlugin from '@layer0/prefetch/sw/DeepFetchPlugin'

skipWaiting()
clientsClaim()
precacheAndRoute(self.__WB_MANIFEST || [])

new Prefetcher({
  plugins: [
    // Enable this as part of the example in this guide
    // new DeepFetchPlugin([
    //   {
    //     jsonQuery: 'picture',
    //     as: 'image',
    //   },
    // ]),
  ],
}).route()

In order to install the service worker in the browser when your site loads, call the install function from @layer0/prefetch.

JavaScript
import { install } from '@layer0/prefetch/window'

install()

If following the Create React App example, this can be done in the same location as App initialization in index.js after the ReactDOM.render call.

Server Side Rendering

React offers a great amount of flexibility in how you set up server side rendering. Frameworks like Next.js offer a standardized, built-in way of implementing SSR. If you’re using Next.js specifically, we suggest using the Next.js guide. We’ll assume at this point that you’re not using Next.js, but have an existing Node app that is doing server-side rendering.

In order to render on Layer0, you need to provide a function that takes a Node Request and Response and sends the HTML that results from the renderToString() method from react-dom/server. Configure that function using the server property of layer0.config.js. Here’s an example:

JavaScript
// layer0.config.js

module.exports = {
  server: {
    path: '0/server.js',
  },
}
JavaScript
// server.js - basic node example

const ReactDOMServer = require('react-dom/server')
const App = require('./app')

module.exports = function server(request, response) {
  const html = ReactDOMServer.renderToString(React.createElement(App, { url: request.url }))
  response.set('Content-Type', 'text/html')
  response.send(html)
}

Express Example

If you already have an express app set up to do server side rendering, the server module can also export that instead:

JavaScript
// server.js - express example

const express = require('express')
const app = express()
const ReactDOMServer = require('react-dom/server')
const App = require('./app')

app.use((request, response, next) => {
  const html = ReactDOMServer.renderToString(React.createElement(App, { url: request.url }))
  response.set('Content-Type', 'text/html')
  response.send(html)
})

module.exports = app

Bundling your server with Webpack

We recommend bundling your server with Webpack. Your webpack config should use the following settings:

JavaScript
module.exports = {
  target: 'node',
  mode: 'production',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, '..', 'dist'), // should match server.path in layer0.config.js
    libraryTarget: 'umd',
    libraryExport: 'default',
  },
  entry: {
    server: './layer0/server.js', // this should point to your server entry point, which should export a function of type (request: Request, response: Response) => void or an express app as the default export.
  },
}

Deploying

Deploying requires an account on Layer0. Sign up here for free. Once you have an account, you can deploy to Layer0 by running the following in the root folder of your project:

Terminal
0 deploy

If you have a static app or are following the above example then you need to build the app first

Terminal
npm run build
0 deploy

For more on deploying, see Deploying.