React Router Integration with Scrivito

If you have a React app and subsequently integrated it with Scrivito, you will need to decide on which content to serve via which URLs. Is all your content Scrivito-based, or do you want your app to also render components independently of Scrivito? In the latter case, in mixed mode, you’d require a routing mechanism, while in a Scrivito-only setting, you usually don't.

For delivering all Scrivito content under a specific URL path (e.g. “/special”), the routingBasePath configuration key is available. Of course, you can use it in other environments as well, e.g. when integrating Scrivito with a PHP-based web app.

In this tutorial, however, we will be using React Router for more flexibility.

Starting at version 1.2, Scrivito features a helper method, Scrivito.useHistory, that, when combined with a history-based routing solution like React Router, allows you to dispatch requests based on the URL path, and to update the browser history accordingly.

Note that the React app on which this tutorial is based has been created using create-react-app, so some file names and imports might be different from your app.

To add React Router to your Scrivito-enabled app, execute in your project directory:

Copy
npm i --save react-router

Then, to route the URL paths to the Scrivito-based or Scrivito-independent content, change your “src/App.js” to:

src/App.js
Copy
import React, { Component } from 'react';
import * as Scrivito from 'scrivito';
import logo from './logo.svg';
import './App.css';
import { createBrowserHistory } from 'history';
import { Router, Route, Switch } from 'react-router';
import Hello from './Components/Hello';

// Create shared history and pass it to Scrivito
const history = createBrowserHistory();
Scrivito.useHistory(history);

function Header() {
  return (
    <header className="App-header">
      <img src={logo} className="App-logo" alt="logo" />
      <h1 className="App-title">Welcome to React with Scrivito</h1>
    </header>
  );
}

class App extends Component {
  render() {
    return (
      <div className="App">
        <Header />
        <Router history={ history }>
          <Switch>
            <Route path="/hello" render={() => <Hello /> }/>
            <Route path="*" render={() => 
              <div>
                <Scrivito.CurrentPage />
                <Scrivito.NotFoundErrorPage />
              </div>
            }/> 
          </Switch>
        </Router>
      </div>
    );
  }
}

export default App;

The code above first provides a browser history object and passes it to Scrivito. Then this history is used in the “App” component when rendering either the “/hello” page or Scrivito-based content using a “Router” switch. In this switch, the catch-all path="*" route matches everything except “/hello”. Of course, you can alternatively handle the Scrivito-based content via one or more routes as well. To keep the code lean, place the Scrivito-related components (the exemplary inner <div> above) in a wrapper component.

Above, the “/hello” URL path is meant to point to Scrivito-independent content, here represented by the “Hello” component:

src/Components/Hello
Copy
import React, { Component } from 'react';
import * as Scrivito from 'scrivito';
import { createBrowserHistory } from 'history';
import { Router, Route, Switch } from 'react-router';

// Create shared history and pass it to Scrivito
const history = createBrowserHistory();
Scrivito.useHistory(history);

class Hello extends Component {
  render() {
    return (
      <Router history={ history }>
        <Switch>
          <Route exact path="/hello" render={() => <h2>Hello!</h2>} />
          <Route path="/hello/world" render={() => <h2>Hello World</h2>} />
          <Route path="/hello/universe" render={() => <h2>Hello Universe</h2>} />
        </Switch>
      </Router>
    );
  }
}

export default Hello;

As you can see, our router switch covers three URL path variants, “/hello”, “/hello/world” and “/hello/universe”. In contrast to the “/hello” route in “App.js”, we are using the exact prop here to prevent the route from being applied regardless of additional path components.

Currently, the “/hello” routes don’t cover nonexistent paths, so you might want to add a catch-all route to the switch above (and render your “NoMatch” component):

Copy
<Route path="/hello/*" render={() => <h2>404</h2>} />

React Router v4 is much more versatile than we were able to convey here. Among the many resources available, we found React Router v4: Philosophy and Introduction to be extraordinarily helpful.

USING REACT ROUTER < V4?

If you are using React Router versions prior to v4, the following is a workaround for history.location:

Copy
Object.defineProperty(history, 'location', {
  get() {
    return history.getCurrentLocation();
  }
});