As developers, we’re constantly faced with decisions that affect the overall architecture of our applications. One essential consideration is where to put logic and rendering in a web application. With the prevalence of mobile devices, cloud computing, and the Internet of Things, efficient and effective rendering has become even more imperative.
Whether you’re building a simple website or a complex web application, you must have a firm understanding of each architectural approach and utilize the correct vocabulary when describing it. The differences between these approaches highlight the trade-offs of rendering from a performance perspective.
In this tutorial, we’ll explore pre-rendering in a React application using react-snap. To follow along with this tutorial, you’ll need a general understanding of JavaScript and React, either npm ≥v5.2 or Yarn, and Node.js ≥v12. Let’s get started!
Pre-rendering is a technique used in web development to improve a webpage’s loading performance by generating and storing the HTML, CSS, and JavaScript before the user requests it. When the user navigates to the page, the pre-rendered version is displayed immediately, resulting in a faster first paint time.
The first paint time, also known as the first contentful paint, is a metric that measures the time it takes for a webpage to display its first graphic content to the user. This can include text, images, or other visual elements. It is an important metric to track because it indicates how quickly users perceive the page loading. A faster first paint time can improve the user experience and reduce bounce rates.
There are a few ways that we can perform pre-rendering, for example, by using a pre-rendering service or a browser API like <link rel="prerender">
or <link rel="prefetch">
.
Pre-rendering improves the perceived load time of the page because the browser can display the pre-rendered content while it is still downloading the remaining resources. It enables search engines to index webpages more efficiently, and it improves the performance of web apps for users on slower networks.
When a search engine crawler visits a website, typically, it can only see the webpage’s initial HTML; it may not be able to execute JavaScript or load resources like images. However, with pre-rendering, the page’s initial HTML includes the page’s full content, including any content that is generated dynamically by JavaScript, thereby making it more likely that the search engine will be able to index the webpage’s content.
Page load speed is also a ranking factor in SEO; search engines like Google give priority to websites that are fast and user-friendly.
Pre-rendering is not suitable for every web application. For example, the pre-rendered version may need to account for user-specific data or dynamic content, which could lead to a poor user experience. Setting up and maintaining pre-rendering requires additional work, so it may not be cost-effective for high-traffic or highly dynamic webpages.
In software development, there is always a trade-off, so it’s important to test and evaluate the impact of pre-rendering on a specific web application and its use case.
You might be familiar with Gatsby or Next.js static export
, which make it convenient to author utilizing components. However, it’s important to distinguish between static rendering and pre-rendering.
Statically rendered pages are interactive without the need for a lot of client-side JavaScript. In contrast, pre-rendering improves the first paint time or the first contentful paint of a single-page application, which must be booted on the client for pages to be truly interactive.
How do we determine whether a given solution is static rendering or pre-rendering? Turn off JavaScript and load the newly created webpages. If JavaScript is not enabled, most functionality will still be available on statically rendered pages. Pre-rendered pages may still have some basic functionality, like links, but the majority of the page will remain inactive.
Another helpful test is to slow the network down using Chrome DevTools to determine how much JavaScript is downloaded before a page becomes interactive. Pre-rendering typically necessitates that more JavaScript becomes interactive, and that JavaScript is more complicated than the progressive enhancement technique employed by static rendering.
Pre-rendering generates and stores a webpage’s HTML, CSS, and JavaScript before the user requests it. On the other hand, dynamic SSR refers to the process of rendering the HTML, CSS, and JavaScript for a webpage on the server in real-time as the user requests it. You can do so by running JavaScript code on the server and using it to generate the HTML sent to the browser. You can use dynamic SSR for static and dynamic web applications, and it allows for handling user-specific data or dynamic content.
The main difference between these two techniques is that pre-rendering is a one-time process that generates static HTML. In contrast, dynamic SSR generates the HTML on demand, allowing it to handle dynamic content. Another difference is that pre-rendering can be done offline, whereas dynamic SSR requires the server to be running and available to generate the HTML in real-time.
Dynamic SSR can be more complex and costly to implement and maintain than pre-rendering. Still, especially for web applications with a lot of dynamic content, it can also be more flexible and powerful.
react-snap is a third-party library that pre-renders a web application into static HTML. react-snap uses Puppeteer, a headless Chrome Node.js API, to generate pre-rendered HTML files for different routes in a web app. react-snap works not just with React applications, but also with Preact and Vue.
react-snap also improves SEO by making webpage content visible to search engine crawlers. Although Google and other search engines can crawl SPAs, it’s not as efficient as crawling static HTML pages, which are already available on page load. Therefore, react-snap generates static pages from SPAs to assist crawlers and optimize this behavior.
Let’s demonstrate how react-snap affects the paint time in web applications. We’ll use Lighthouse, an automated, open source tool for improving a webpage’s quality. We’ll generate a performance report showing the difference in first paint times of a React application, both with and without react-snap integrated into it.
To begin, let’s create the first React demo without react-snap:
npx create-react-app react-bookstore
Replace the default syntax in App.js
with the following code:
import "./App.css"; function App() { return ( <div className="App"> <h1 className="title">React Bookstore</h1> <div className="bookstore"> <div className="books"> <img src="book1.jpg" className="booklogo" alt="Book" /> <h1>BOOK TITLE 1</h1> <p><b>AUTHOR:</b> AUTHOR 1 </p> </div> <div className="books"> <img src="book2.jpg" className="booklogo" alt="Book" /> <h1>BOOK TITLE 2</h1> <p><b>AUTHOR:</b> AUTHOR 2 </p> </div> <div className="books"> <img src="book3.jpg" className="booklogo" alt="Book" /> <h1>BOOK TITLE 3</h1> <p><b>AUTHOR:</b> AUTHOR 3 </p> </div> </div> </div> ); } export default App;
Next, we’ll generate a production build and run an audit to get the performance report for the first demo:
The performance report states that the demo has a performance score of 87 percent, where the first paint time is generated in 0.5s. This is alright, but we want our app’s performance to be as efficient as possible. Let’s reduce the first paint time by integrating react-snap into our React application. Install react-snap with the following code:
npm install --save-dev react-snap
Then, we’ll add a postbuild
script in our package.json
:
"scripts": { ... "postbuild": "react-snap" }
This automatically runs the react-snap command every time a new build of the application is made. Next, we’ll change how the application is booted. Replace the src/index.js
file with the following code:
import React from 'react'; import { hydrate, render } from "react-dom"; import './index.css'; import App from './App'; ReactDOM.render(<App />, document.getElementById('root')); const rootElement = document.getElementById("root"); if (rootElement.hasChildNodes()) { ReactDOM.hydrate(<App />, rootElement); } else { ReactDOM.render(<App />, rootElement); }
Instead of using only ReactDOM.render
to render the root React element directly into the DOM, the code above checks to see if any child nodes are already present, which determines whether the HTML content was pre-rendered or rendered on the server.
In this situation, we use ReactDOM.hydrate
to attach event listeners to the previously created HTML rather than creating it from scratch. We’ll generate a performance report for the new React application with a performance score of 100 percent and a first paint time of 0.4s:
minimalcss
with react-snapThe issue of a flash of unstyled content, or FOUC, in rendered, static HTML can be a significant disadvantage. A flash of unstyled content is an instance where a webpage appears briefly with the browser’s default styles before loading an external CSS stylesheet. This is caused by the web browser engine rendering the page before all information is retrieved.
To prevent this, you should place all the CSS required to render the page in the <head>
section of the HTML document.
react-snap uses another library, minimalcss
, to extract the CSS needed to render webpages faster at initial load. To enable minimalcss
, update your package.json
file with the code below:
"reactSnap": { "inlineCss": true }
Keep in mind that the inlineCSS
option is still experimental at the time of writing. I recommend double-checking to ensure that all styles are applied correctly for your routes.
Let’s review some alternatives to react-snap.
Prerender SPA plugin is a webpack plugin that converts webpages into static pages. It serves as a flexible, framework-agnostic static site generator for websites and SPAs. It provides a simple pre-rendering solution that is easily scalable can be used for any site or single-page app, and makes indexing less complicated.
React Snapshot is a zero-configuration, static pre-renderer for React apps. It takes static site snapshots of all publicly accessible webpages, leaving anything that requires authentication as a standard, JavaScript-driven single-page app. The default snapshot delay is 50ms, which you can change to suit your app’s requirements.
Pre-rendering is a powerful technique that offers numerous benefits for web applications. Whether you’re a seasoned web developer or still a newbie, understanding the basics behind pre-rendering and knowing how it can improve your web applications is essential to building high-quality, user-friendly applications that meet your user’s demands.
In this article, we explored how to pre-render a web application into static HTML using react-snap. I hope you enjoyed this article, and be sure to leave a comment if you have any questions.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
3 Replies to "Pre-rendering your React app with react-snap"
Hi there I really enjoyed reading your post. I’m a little confused in the section Pre-rendering in modern web applications.
Don’t you pre-render when the user requests it, not before?
Will this work for module federation Applications (MFA)?
This library, react-snap, was already 4 years unmaintained by the time you wrote this.