Porting a Multiple-Page Application to React: A Piecemeal Approach with React Router 4
Whatever the reason, you’ll need a plan before you start ripping down all that perfectly good (or at least functioning) code and replacing it with this season’s new Derelicte range. If you’re happy enough to jump in, rewrite your whole application in React, deploy it all a few weeks (months?) later and cross every finger, toe and appendage on your body, please don’t let me stop you! If however you’re like me, and you would like to migrate to React over time to gauge how it affects the performance and usability of your application, read on for a suggestion in how to tackle this.
Our Starting Point: The Trusty and Rusty MPA
As an example we’ll take a look at a simple MPA with three pages: home, about and books. These will be served up using express.
Ok, nothing much to see here. Running the server and navigating to
localhost:8080/books we’ll see the index, about and books pages respectively. Using the navigation links on each page will take us to each page as expected. We’ve repeated the navigation HTML in each file, but hopefully we are using some intelligent MVC framework that would allow us to DRY away our tears and carry on with the task at hand.
Introducing React on One Page
We’ve decided as a business to convert the home page of our application to use React. This makes sense as just about everyone visiting will land here first with the exception of those who have bookmarked some other page (more on that later). The more people who see our new tech in action, the more feedback and data we can collect, the more we can do to improve the experience. Let’s create a new React application consisting solely of the home page, leaving everything else as is.
Above, we have plucked the code from our original index.html and created a React component that does the work for us instead (in the form of App.jsx). The index.js wires up our new React application to the new index.html page that is served by the same express server we made earlier. Whenever somebody accesses
localhost:8080 they will hit the
/ route in the server which will respond with the index.html, which runs the bundled React application. If you would like to understand more about how that bundling is happening and where that file is coming from please do take a look at the project on github. The only thing that is essential to understand for now is that the extra code added to server.js above will statically serve the output of webpack (bundle.js).
After running the server and clicking around for a while you might be inclined to think your job is done. You might also think that the whole thing was really quite easy and this author has a bright future selling fruit found only at knee height and below. Perhaps we have not considered our favourite mid-afternoon-meeting filler phrase:
Will this solution scale?
Introducing React on a Second Page
In reality we only need this idea to “scale” to the number of pages we plan to migrate to React, so maybe it’s a silly question. Let’s imagine we naively try out the same idea on the books page.
We would have to change the books.html to reference some Webpack bundle, but if this is a separate page then it needs to be a separate React application which will need to be a separate bundle. Ok, fine, we say. Not so fine though, there’s some overhead associated with creating a React application; there are extra dependencies to send down the wire. If you make a full SPA, this penalty is less noticeable when compared to the amount of application code you will also send with that first request. But if you make two SPAs then you will end up with this penalty twice; once on the home page and once on the books page. That is not so great.
Let’s continue to bury our heads in the sand and ask how we would go about developing such a convoluted creature? Will we now need to have two different processes running so that changes can be picked up and code can be re-bundled for each React application? That doesn’t sound easy to manage with two pages, never mind when we are extending this idea to maybe five or more.
And what about code reuse? Sure, we would get to reuse react components between pages, but we would have to manually include things like the navigation in each page we port to React. There should be a way of saying “I want this thing to always be here”. If not for simplicity then at least so that we aren’t wasting even more bandwidth in sending the bundled code snippets every time a different React application is loaded.
We need a well structured approach that addresses the concerns above. Sure, the naive attempt will work but it will just limp along, making your development life cycle, your code quality and your end product all worse in the short and long run.
Introducing React Router 4!
Last week I was reading the React Router docs and familiarising myself with some aspects of it I hadn’t used so much. Today I started writing this article. Imagine my surprise when I tried to find the same documentation I had been reading last week, only to find it had all completely disappeared and been replaced with new, shiny (and slightly different) docs! A few hours earlier React Router 4 had been released. This is the crazy software world we live in and you just have to roll with the punches. No better time than the present to start using the new library and demoing how it can assist us with some of our grievances from the previous section.
Let’s have a go at replicating what we have done so far with React Router.
We have moved the content of the home page from the
App component to a
Home component. Now
App is only concerned with setting up the router and home route. Some of React Router 4’s new API is creeping in here already:
- We are importing all of our dependencies from the react-router-dom package
Routenow takes an
exactprop, fulfilling a similar purpose to
IndexRoutein older versions of React Router.
- We are using a
BrowserRouter, which provides us with a router already endowed with the HTML 5 history API. This is a nice convenience over having to fiddle around with imports and supplying the history you wish to use to the router.
With the updates above, our application should function in exactly the same way. But have we made it any easier to port a second page to React? Yes!
Introducing React on a Second Page: Part Deux
Let’s proceed with the pattern we established above.
We have introduced a new route for the books page in App.jsx. We have also had to wrap our routes in a
div . There are reasons for this, and it’s part of React Router’s new uber declarative approach. It should make sense later, but for now, you can accept that React Router will start chucking errors at you if you omit the wrapper
We have also added a
Books component with a few small differences. Notice that two of the
a elements are now using React Router
Link . This allows us to make use of the fact that the books and home pages are now part of one single SPA. Clicking the “Home” and “Books” links will dynamically update our view, whereas clicking “About” will still cause a full page reload. This means we can now delete our
/books route from the server, along with the books.html (please do this or the remainder of the article may stop making sense), all of this code now lives in the front-end of our application. In addition we will need to update the home page to make use of these fancy SPA features:
Now if you start the application and click around everything will seem great. You can navigate from home to about, about to home, home to books, books to home, books to about and….
Cannot GET /books
Oh boy. That “Books” link on the about page didn’t work so well. Is it the
Link that’s the problem? Go back to the home page, navigate to books. Now click reload in your browser. That didn’t work so well either did it? What if a user has bookmarked this page? The problem is that the
/books route only makes sense to React Router. The moment we are lifted out of SPA land and forced to deal with application refreshes or
a tags pointing us at a
/books URL it isn’t React Router who deals with it, it’s the server. Luckily, we can update our server to handle our front-end routes and redirect us to the SPA, which will hand our routing back over to React Router and take us to the correct page!
Now, any route except for
/about will be redirected to the SPA and handled by React Router. There are obviously drawbacks with the rather heavy-handed way I’ve implemented this above (what happens if someone types an invalid route in the browser?) but it serves its purpose for this demo.
A Small Refactor
It looks like we have now addressed most of our concerns:
- We have one SPA, so development should be pretty easy to manage.
- We aren’t sending the dependencies for React (and any other library) down the wire multiple times.
But what about the code reuse issue? We still include the navigation in every page component, and in the world of software development,
Repetition is the key to misery.
(I’ll let you know if I ever figure mastery out.)
React Router can help us unlearn our old habits of declaring routing separately to the rest of the application. We can declare our navigation as static (with respect to the router) and simplify our page components to only contain the portions of the view that change.
The purpose of the containing
Router might be clearer now that the router is responsible for rendering a route component and arbitrary JSX as siblings.(A react component’s render method must always return only one element).
This has massively simplified each of our page components and made our code DRYer than Antarctica. (and cooler. Ha.)
Are We Done?
Why yes, I think we are. Now if you want to port another page to React you simply:
- Write the page component using React
- Reuse any componentized goodies that you have written for other pages
- Add the route to React Router
- Update any internal SPA navigation to your page to use React Router’s
Link. Leave navigation from other pages as is.
- Delete the corresponding HTML you were previously serving up
- Adjust your server routes accordingly (This might mean preserving the routes on the server rather than taking the sledgehammer approach like I did)
And that should be everything!
Best of luck with your journey, and welcome to this brave new world of SPAs. There’s a whole lot more to learn and you’re just getting started!