Abandon SPA, embrace page-as-a-component

Our solution was to abandon the single page app model, and instead let Django serve each page individually, with a root React component for each page. Our base site components that don’t change between pages (e.g. navbar, footer) are provided by the Django templates, and the content specific to each page (e.g. poker interface, leaderboard) are composed within React.

Abandon the SPA, why would you say such a horrible thing?!

Single page apps are not always the solution , we’ve gained stability (bugs are limited to only one page), easier debugging, easy search engine indexing, and easier static page management by having page boilerplate and routing handled by Django

, we’ve gained stability (bugs are limited to only one page), easier debugging, easy search engine indexing, and easier static page management by having page boilerplate and routing handled by Django It’s much easier to create non-React pages for static content (e.g. about page, login page) when you have all your page boilerplate in Django templates

No need to deal with React routers, the History API, or async fetching of page content behind the scenes (more on how we do page hotloading without refreshes in a later post)

So what does this look like in practice?

The Django Template

Here we have our base template used for React pages, notice how it extends ui/base.html (which contains our header, navbar, and all the usual things you put in a base template).

This base template can accept any combination of JS component file and props. If you wanted you don’t even have to pass a React component, you could pass in a Vue.js or Angular script, and have it render to window.react_mount and read window.props .

But this page is empty, how do I render my React components?

The {{component}} is a path to a script, e.g. leaderboard.js , which contains a standard React component, rendered using ReactDOM. The script should be compiled and readily accessible by browsers from your staticfiles folder. It can be aggressively cached by your backend and nginx, since the contents never change, all dynamic content is passed via window.props .

So far this gives us a basic page with a navbar, and a loading message. Once we have a component to mount, the loading message is replaced by the component (this process happens so fast your users will never see the Loading Components… message).

The React Component

Now we define a basic React component which lays out our page. We can import reusable components from other JS files, or just write it all in one file. I’ll do that here for simplicity. A more complex page may mount a redux Provider, import dozens of components, or setup websocket connections here. Think of this as your ‘ base.js ’ entry point, similar to an SPA base.js , except it only defines the contents of one page.

Here I implement a simple leaderboard, which shows a list of users.

Building the Javascript Component

We compile this source file from ES6 to ancient JS using browserify, and stick it in our staticfiles folder so it can be served (and cached) easily. I chose browserify for simplicity, but you can use webpack in your project and get the same results.

browserify src/pages/leaderboard.js > static/src/pages/leaderboard.js

Great, now we have something on the screen!

But where did Alice & Bob come from, how did we pass data to the template?

The View

The Django view just needs to provide the following context to the react_base.html template in order to render this page properly:

component = leaderboard.js (our React file above) props = {users: [{username: ‘alice’}, {username: ‘bob’}]} (the data passed to the React component as props) title = ‘Leaderboard’ (used for the browser tab title)

Why use this pattern, are there alternatives?

This simple pattern of mounting components inside of a base template which passes things to React is very powerful. It lets you freely mix and match template composition with React components, depending on which you need for a given page.

But we already built our whole frontend on top of an intricate REST API with endpoints for everything from user info to page content, why is this better?

Don’t go and rewrite your whole frontend if you already have a REST API-based SPA. You get some advantages with REST API + SPA that you don’t get with page-as-a-component, such as reduced bundle sizes because webpack can optimize shared code across pages. It might also be easier to scale if you split your backend functionality into separate micro-services. The page-as-a-component approach is great if you’re ok with slightly larger bundles, in order to get flexibility and nice interplay with Django templates and routing.

What are my options if I’m building an app from scratch these days?

SPA + granular API: Use Django as a REST API backend, and provide all your page data via API endpoints that are queried by a monolithic SPA React app (this is the approach I see taught online the most). This approach is great for a microservices-style backend, because the frontend doesn’t care how the backend is built, or what endpoints it hits, as long as it gets the data it needs.

- Webpack is optimized for this approach.

- Frontend can be built on top of lots of separate microservice endpoints

- Frontend handles routing & switching to new pages

- Very hard to nail server-side rendering (could be bad for search indexing)

- Loads slowly on bad connections since data takes many round trips

Use Django as a REST API backend, and provide all your page data via API endpoints that are queried by a monolithic SPA React app (this is the approach I see taught online the most). This approach is great for a microservices-style backend, because the frontend doesn’t care how the backend is built, or what endpoints it hits, as long as it gets the data it needs. - Webpack is optimized for this approach. - Frontend can be built on top of lots of separate microservice endpoints - Frontend handles routing & switching to new pages - Very hard to nail server-side rendering (could be bad for search indexing) - Loads slowly on bad connections since data takes many round trips SPA + 1 request-per-page API: Provide each page’s data as one request The frontend still handles routing, but the backend is built with 1 view per page, instead of many smaller REST API endpoints. It’s generally easier to start this way rather than exposing separate endpoints for every query, but it couples your backend & frontend logic fairly tightly.

- Frontend handles routing & switching to new pages

- Frontend relies on all almost its data coming from one endpoint

- Faster load times, easier caching than having separate endpoints

- Easier to do server-side rendering because views match the frontend data

- Decent performance on bad connections, but still requires 2 round-trips

Provide each page’s data as one request The frontend still handles routing, but the backend is built with 1 view per page, instead of many smaller REST API endpoints. It’s generally easier to start this way rather than exposing separate endpoints for every query, but it couples your backend & frontend logic fairly tightly. - Frontend handles routing & switching to new pages - Frontend relies on all almost its data coming from one endpoint - Faster load times, easier caching than having separate endpoints - Easier to do server-side rendering because views match the frontend data - Decent performance on bad connections, but still requires 2 round-trips Templated JS Snippets: Compose React components using the template system, by including fragments of JS code to build up an html template that renders the desired page (instead of mounting one component file with the whole page defined in JS). This approach requires the most haxxing in my experience, as you totally ignore the benefits of JS imports and break your ability to lint pages, minify JS, and compile pages to the minimum amount of JS needed. This is the approach libraries like django-react-templatetags use, it allows for server-side rendering, but it introduces a lot of complexity (including a node server!) into your backend.

- No AJAX requests needed, all data is served with the page

- Backend handles all routing, frontend links are normal hrefs

- Server side rendering is very easy, data is all provided by one view, and snippets can be rendered independently and cached

- Easy to swap out react components for non-react on a piece-by-piece basis

- Very fast load times, rendered snippets can be cached and served together with their data (great for search indexing)

- 1 round-trip page-loads if html page is cached

Compose React components using the template system, by including fragments of JS code to build up an html template that renders the desired page (instead of mounting one component file with the whole page defined in JS). This approach requires the most haxxing in my experience, as you totally ignore the benefits of JS imports and break your ability to lint pages, minify JS, and compile pages to the minimum amount of JS needed. This is the approach libraries like django-react-templatetags use, it allows for server-side rendering, but it introduces a lot of complexity (including a node server!) into your backend. - No AJAX requests needed, all data is served with the page - Backend handles all routing, frontend links are normal hrefs - Server side rendering is very easy, data is all provided by one view, and snippets can be rendered independently and cached - Easy to swap out react components for non-react on a piece-by-piece basis - Very fast load times, rendered snippets can be cached and served together with their data (great for search indexing) - 1 round-trip page-loads if html page is cached Page as a Component (our approach): Compose pages in JS, include each page as a cacheable <script> in the template, pass it data via props. This approach combines the best of both worlds, you get page composition in React, but also easy server-side rendering and caching of whole pages at a time. It’s also easier to manage the data flow when you don’t have separate react snippets, as all components are laid out hierarchically from one mount point downwards.

- Slightly harder to mix & match template fragments with React components

- More customizable build process than with Templated JS Snippets

- Flexible, works equally well for non-React pages like Vue.js or Angular

- Easier to manage data flow bc. of single JS entry point (esp. with redux)

- 1 round-trip page-loads if script bundle is cached

(our approach): Compose pages in JS, include each page as a cacheable in the template, pass it data via props. This approach combines the best of both worlds, you get page composition in React, but also easy server-side rendering and caching of whole pages at a time. It’s also easier to manage the data flow when you don’t have separate react snippets, as all components are laid out hierarchically from one mount point downwards. - Slightly harder to mix & match template fragments with React components - More customizable build process than with Templated JS Snippets - Flexible, works equally well for non-React pages like Vue.js or Angular - Easier to manage data flow bc. of single JS entry point (esp. with redux) - 1 round-trip page-loads if script bundle is cached Have a pattern your team uses but that I didn’t mention? I’m always looking for new ones, contact me @theSquashSH and I’ll include it here.

Extending the Page-as-a-Component pattern

Ok, the Lilliputians are satisfied with the design, but they want more features!

The code samples above are overly simplified. A real app in 2017+ probably mounts Redux providers, creates WebSocket connections, fetches content over the wire, and imports many dozens of components to assemble a full page.

Now that I’ve introduced a basic pattern, it’s time to think about extending it with the next layers of functionality. Depending on your application you may decide to add:

More advanced, extensible page views on the Django side

Adding some props shared by all pages (e.g. current user’s info, viewing language, time-zone, get_base_props(request) )

) A redux store for managing frontend state

WebSocket connections to Django using django-channels , where the handler code lives next to the page’s view (check out our django-channels-router library)

, where the handler code lives next to the page’s view (check out our django-channels-router library) real-time updated react animations with backend time-synchronization

Hot-loaded new pages when you click links without refreshing. This is one of the strong points of an SPA with react-router , but we can get pretty close without needing to go full SPA:

1. Load the the JS component file for the next page by appending a new script tag to <body>

2. query an endpoint that serves that page’s props as a JSON response , and save them to window.props

3. Render the new page with:

ReactDOM.render(<NewPage {...window.props}/>, window.react_mount)

Hot-loading can be hard to get right though, recovering from errors cleanly and falling back to normal navigation difficult to do while loading JS scripts and querying API endpoints that could fail in numerous ways independently. You’ll probably also have to mess with the History API to change the URL and browser title when navigating.

You can ping me on twitter @theSquashSH if you have any questions, or check out our site in action at https://oddslingers.com!

In our app (a poker site) we use django-channels to send data over a WebSockets to a react-redux frontend. The frontend animates some game state in real-time, while remaining time-synchronized with all the other site viewers to within ~25ms. We didn’t find any libraries that pulled off web animation in a way that we liked, so we ended up implementing our own: redux-time . It’s worth checking out if you’re trying to do real-time animation in a functional, declarative way with redux -style state management. We also developed django-channels-router which is the backend routing to handle socket messages to/from the frontend. Let me know if you find either library useful, or if you’ve worked on anything similar in the past!

Links and Resources: