Colin McDonnell @colinhacks
published October 29th, 2020
Edit: 2022 The issues described here have been largely solved by layouts in Next.js 13.
I recently set out to implement a single-page application (SPA) on top of Next.js.
To clarify, I'm using the term "SPA" in a very specific way. I'm referring to the "classical" SPA model: an application that handles all data fetching, rendering, and routing entirely client-side.
Turns out, Next.js does not make this easy.
To actually learn how to build a single-page application on top of Next.js, checkout my tutorial! Building a single-page application with Next.js.
Next's built-in router isn't as flexible as React Router! React Router lets you nest routers hierarchically in a flexible way. It's easy for any "parent" router to share data and provide an outer layout to its "child" routes. This is true for both top-level routes (e.g. /about
and /team
) and nested routes (e.g. /settings/team
and /settings/user
).
This isn't possible with Next's built-in router! Instead, all shared state and layout must be initialized in your custom _app.tsx
component. It's equivalent to building an app with a single, top-level React Router. Nominally, Next.js lets you define "nested routes" (e.g. pages/settings/team.tsx
), but in practice it "flattens" all those routes. The only code that's shared is the stuff you can fit into _app.tsx
.
There are some established patterns for circumventing this limitation, notably those described in this post by Adam Wathan. If you are dead-set on using Next's routing system, you should look at those approaches. But in my opinion they're agregiously hacky.
I finally got React Router to work inside Next.js — check out my tutorial for details — but there were some thorny issues along the way. The guidance in the documentation is...scant, to put it mildly. It boils down to "use a custom App".
That's it. Not particularly enlightening. Or detailed.
I found this to be pretty strange. Aren't SPAs still the dominant model for building React-based applications? Didn't Next.js just run a wildly successful conference with 60,000 virtual attendees? Didn't they just raise a \$21M Series A? Am I the only person struggling with this? Why is this so hard?
My answer: Vercel doesn't really want you to build SPAs, at least not with client-side routing. Theyr're a server hosting company, so they want you to use servers. To that end, I believe they consciously emphasize the server-side rendering (SSR) approach and de-emphasize the SPA approach.
An astute reader may ask "Why would Next.js try to de-popularize SPAs but encourage static site generation (SSG)?" Like SPAs, statically generated sites don't involve servers and thus doesn't make Vercel much money. Good point. This is where my theory veers into conspiracy and madness.
I suspect SSG is a strategic marketing play. A huge fraction of developers will build a static website at some point in their career. Having built their personal website with Next.js, they're more likely to choose Next.js down the road when designing their shiny new SaaS app. And since it's against Vercel's fair use policy to run a commercial enterprise on the free tier, all these new companies will eventually convert to a paid plan. They're playing the long game.
I don't think the SSG functionality in Next.js was created solely for marketing reasons; that's ludicrous. I think Next.js is the best way to build static site; I wrote a 1500-word article to that effect just 5 months ago. In fact, I believe Vercel is laser-focused on improving the developer experience, perhaps more than any other company. That's exactly why I don't believe the lack of documentation on client-side routing is merely an oversight. The strength of the rest of their documentation makes this hole even more glaring.
None of this is bad, per se. Vercel and Next.js originated, in part, as a reaction to the SPA-centricity of React development pre-2016. They're under no obligation to document, advertise, or encourage the usage of SPA paradigm on their platform, especially if it works against their own interests. And all told, their free tier is extremely generous.
But remember that, at the end of the day, Vercel is a hosting company with a vested financial interest in pushing server-side rendering and de-popularizing SPAs. If someone tried to learn full-stack web development by reading the Next.js documentation, they wouldn't even know that SPAs are an option. So keep that in mind as you design your next project!
Addendum: The section below was added shortly after initial publication.
In response to this article the lead maintainer of Next.js, Tim Neutkens, provided this counterpoint on Twitter:
All our marketing and communication around this have been around making sure you don’t use SSR if you don’t need it, e.g. by adding revalidate (incremental generation) and fallback (on-demand generation)
— Tim (@timneutkens) October 29, 2020
This actually sheds light on another interesting aspect of Vercel's SSG push.
Recently Next.js has introduced support for "dynamic static generation" with the fallback
flag and incremental static regeneration with the revalidate
flag. Tim presented these features as evidence that Next.js is helping developers avoid server-side rendering if it isn't absolutely necessary. But using either of these features requires Vercel to spin up a serverless function for your app. In other words, Next is blurring the line between SSG and SSR.
If you rely on these features, you've left the "pure" SSG paradigm. Your app now requires a server to work as expected — which means Vercel stands to make money! You can no longer simply next build && next export
your app and deploy to your static hosting service of choice. Moreover, if your project is commercial in nature, you now must upgrade to a Pro plan (if hosting on Vercel).
If you want to actually learn how to build a single-page application on top of Next.js, checkout my tutorial Building a single-page application with Next.js .
Hopefully I didn't come off as too critical of Vercel. Next.js is phenomenal and I love it. If I didn't I would never have gone through all this trouble!
The "comments section" for this post is this Twitter thread. Jump in and yell at me for all the bad takes in this post!
✨ NEW BLOG POST ✨
— Colin McDonnell (@colinhacks) October 29, 2020
It started as a simple "How to build a SPA with Next.js" tutorial.
It turned into a fevered rant about Vercel, Next.js, perverse incentives, and the future of the SPA paradigm. Oops.https://t.co/hrRtKojezH
If you're into Next.js or TypeScript, follow me on Twitter @colinhacks! I build and maintain open-source tools like Zod (a TypeScript validation library) and tRPC (a tool for building end-to-end typesafe APIs with TypeScript). Or subscribe to the newsletter to get notified when I publish new posts!
Colin McDonnell @colinhacks
published October 29th, 2020
Subscribe