https://www.causal.app/blog/next-js
[61f289532e][623d980c68]
We raised a $20m Series A led by Coatue + Accel! Click here to read
the announcement.
[61e96266d5]
Causal for Finance
Causal for FP&ACase Study: ClassDojoCase Study: Marley SpoonCase
Study: PrefectCase Study: Inscribe
TemplatesIntegrationsPricingCareersLoginCreate accountBook a demo
LoginCreate accountBook a demo
Engineering
How We Improved React Loading Times by 70% with Next.js
By switching from CRA to Next.js, we reduced our initial page load
times by 70% and unlocked a new level of developer experience.
Causal
Causal
October 23, 2022
Causal is a multidimensional spreadsheet that's capable of handling
everything from basic arithmetic all the way up to
billion-calculation financial models. The Causal frontend was built
with Create React App (CRA) in 2019, and it served us well - it
required minimal initial setup and allowed us to iterate quickly. As
our customers grew in size and complexity, and performance became
more of a concern, we reached the limits of what CRA was designed to
support. Most importantly, CRA doesn't natively support route
splitting across multi-page apps, so our page load times were
frustratingly slow. To solve these problems, we switched to Next.js,
reducing our initial page load times by as much as 70% and unlocking
a new level of developer experience.
Contents
What is Next.js?
Next.js is a framework that comes with build tools and runtime
libraries for creating rich React applications. It has the same
functionality as CRA, but also includes built-in support for key
functionality that CRA is missing: page routing, intelligent
pre-loading based on page contents, and hybrid static & server-side
rendering.
Migrating from CRA to Next.js
In mid 2022, we determined that the benefits of migrating from CRA to
Next.js would be worth the time investment. We were particularly
excited about having its built-in page routing primitive, so we
wouldn't have to manually configure our routing and Webpack builds.
Routing
We'd previously needed to set up our own in CRA using react-loadable
and react-router + react-router-dom, including a large routes.tsx
file that explicitly set up a routed component for each page in the
app:
import Loadable from "react-loadable";
import { Route, Switch } from "react-router-dom";
const EditorLoadable = Loadable({
loader: () => import(/* webpackChunkName: "routes-editor" */ "pages/editor"),
loading: ChunkLoading,
});
export function RouteSwitch() {
return (
}
/>
...
);
}
One of the advantages of Next.js over CRA is that Next.js ships with
its own integrated linking & routing solution, next/router. Placing a
file at pages/model/[:id]/edit.tsx with a default-exported React
component is all Next.js needs to know to render a page at that path,
with an id prop indicating the URL's id.
Furthermore, the built-in Next.js Webpack configuration automatically
splits pages out into their own bundles. That means visiting a page
for local development only needs to build the bundle contents
required for that page. Although CRA does support code splitting, in
our experience the Next.js configuration is out-of-the-box much
faster for local rebuilds.
Styles
Many of the older CSS files in the Causal codebase had been written
prior to the team standardizing on CSS modules best practices. A
number of those files utilized "impure" CSS selectors, meaning they
could impact elements rendered by components elsewhere on the page.
For example, our previous Button component unintentionally targeted
all buttons on the page:
// styles/button.scss
button:disabled {
cursor: not-allowed;
}
// components/Button.tsx
import "styles/button.scss";
export function Button(props) {
return ;
}
We switched global CSS styles to CSS modules wherever possible. This
allowed components to be more explicit about which styles they're
taking in.
For example, we switched our Button component to a scoped class name
in its CSS module:
// components/Button/styles.module.scss
.button:disabled {
cursor: not-allowed;
}
// components/Button/index.tsx
import styles from "./styles.module.scss";
export function Button({ className, props }) {
return ;
}
Note: switching to "pure" CSS modules also significantly improved
build times in the CRA app before the switch to Next.js was
finalized. Many .scss files had been also using @use and @extend SCSS
directives to build up styles using other, shared .scss files. Those
directives caused the shared files to be rebuilt as a part of each
file that included them - causing multiple seconds of build time each
for some of the bigger files!
See this Next.js discussion answer on pure modules for more
information.
Deploys
Once we had Next.js working locally, the next step was to change our
deploy strategy.
Here, CRA and Next.js have fundamental differences. The build output
from CRA is just static files, so serving it is relatively
straightforward. The build output from Next.js does include some
static files, but it can also include code to run a separate server.
This server is responsible for serving redirects, server-side
rendering dynamic pages, and also serving static pages.
When evaluating the options for deploying our new Next.js frontend,
we settled on three possibilities:
1. Don't use any server-side rendering for Next.js, build using next
export, and treat the output exactly the same as CRA's static
output.
2. Host the entire frontend on Vercel, pointed at our backend
(hosted in GCP).
3. Write a custom Docker image for the Next.js server, and host it
in GCP alongside our backend and other services.
Each option had pros and cons:
next export
* Pro: almost 0 work to set up (identical to CRA output)
* Con: does not support server-side rendering
Host on Vercel
* Pro: minimal setup required
* Con: no official support for Yarn 2
* Con: cannot easily connect to database for faster server-side
rendering
Custom docker image
* Pro: maximum flexibility for server-side rendering dDirect DB
connection is possible, backend API calls will be very fast
because of colocation on GCP)
* Pro: most granular control over resources required/used
* Con: maximum setup required: Vercel provides examples but they
don't work out of the box; Kubernetes routing/networking,
scaling, etc. all need custom setup
Given our desire for maximum flexibility, we chose option 3: writing
a custom Docker image. (We do still deploy to Vercel, though - more
on that later!). We do a small amount of server-side rendering on a
few pages, and we have found the performance to be excellent so far,
in large part thanks to the minimal network distance required to talk
to our other services.
Preview Apps
Although Vercel wasn't feasible for our production deploys (as
explained above), it is still quite useful for its preview apps.
Although setting the Vercel build process up required a couple of
workarounds (for the aforementioned lack of Yarn 2 support, and to
build a common package used in our frontend), the benefit is immense:
every commit pushed to our GitHub repository now gets built and
deployed on Vercel as a preview app, pointed at our staging backend.
This has enabled an order of magnitude improvement in the code review
experience for frontend changes. Instead of requiring pulling
branches locally to test, reviewers can just click a link in the PR
they're reviewing and preview exactly what the branch will look like
in production.
Although this change did not require Next.js, it was a breeze because
of Vercel's native support for its own framework.
Results
Switching to Next.js unlocked significant improvements in both
end-user and our developer experience:
Causal models are typically created by a few people but viewed by
dozens of others; these viewers look at the model dashboard. And
without any effort into advanced server-side rendering (using e.g.
getServerSideProps), load times for these dashboards decreased by 32%
(2.6s - 1.5s)!^1
Simpler pages have even more dramatic speed-ups. For example, our
home page (my.causal.app) loads 71% faster (1.7s - 0.5s), with no
layout jump except the necessary transition from the loading state to
the loaded state.
The performance benefits extend beyond just the user experience.
Next.js has a significantly faster development experience than CRA;
developers benefit from 30% (or more!) faster start-up times, and the
fast refresh experience is a game changer for quickly iterating on
small UI tweaks. By far the largest improvement came from pull
request preview apps -- a significant improvement to the code review
experience. It takes seconds instead of minutes to preview frontend
code changes, which has allowed us to give more frequent reviews on
smaller pull requests, and also allowed our customer success team to
provide feedback earlier in the development process.
Next Steps
We're thrilled to see the app running in production on Next.js. Our
page loads are significantly faster, our local builds take seconds
instead of minutes to get started, and the amount of Webpack
configuration we need to maintain is dozens of lines instead of
hundreds.
We plan on implementing even more server-side rendering soon,
starting with embedded charts and tables, which are typically viewed
by anonymous visitors. We expect to see a significantly improved
experience for these users as a result of faster load times.
Of course, performance in modern web apps is about much more than
first load times. Even more important is the performance of user
interactions, which are particularly difficult to optimize in Causal
because we are a data-heavy application rendering complex grids,
charts, and tables. In a future blog post, we'll share more about how
we solve those performance problems.
If you're interested in working on challenges like this with us, you
should consider joining the Causal team -- check out our careers page
or email lukas@causal.app!
Thank you to Joshua Goldberg for instrumental help with this
migration!
Email Address[ ]Company Name[ ]
First Name[ ]Last Name[ ]
[Get model for free]
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
[]
^1: We're measuring First Contentful Paint
View all blog posts
Engineering
How We Improved React Loading Times by 70% with Next.js
By switching from CRA to Next.js, we reduced our initial page load
times by 70% and unlocked a new level of developer experience.
[placehol]
Causal
October 23, 2022
Engineering
DevOps Challenges
At Causal we're constantly pushing the limits of our platform,
requiring creative and smart problem solving to overcome those
hurdles.
[placehol]
Lukas Kobis
August 8, 2022
Engineering
Scaling our Spreadsheet Engine from Thousands to Billions of Cells
From Maps to Arrays
[placehol]
Causal
July 5, 2022
Get started today with Causal
Sign up and get started for free, or book a demo to learn how Causal
can transform your finance function.
Start your free trialBook a demo
[61e96266d5]
PRODUCT
Documentation
Updates
Templates
Integrations
COMPANY
Blog
Engineering Blog
Careers
Community
SOC 2
Security
Privacy Policy
Causal for Finance
Causal for Finance
Case Study: ClassDojo
Case Study: Marley Spoon
Case Study: Inscribe
Startup Finance Jobs
(c) 2022 Causal, Inc. All rights reserved.
[61e962652b][6]
websights