Render anything statically with Next.js and the Megaparam

Eric Burel
VulcanJS
5 min readNov 8, 2021

--

🇫🇷 Speak French? Cet article est disponible en français sur PointJS.

You can render anything statically. Or can you?

Earlier this year, I’ve published an article about how you can render almost anything statically, at build-time, using Next.js. My goal is to promote “Rainbow Rendering”, the ability to statically render multiple variations of the same page. If you want your home page in seven different possible colors depending on the user’s mood, you can!

At the time I wrote this, it was quite difficult to implement though. You needed an upfront server to process the request, in particular for securing paid and private content. You also had no way to keep the URL clean.

When not using Rainbow Rendering, you usually check authentication within getServerSideProps during SSR (worst anti-pattern ever, don’t do that), or you secure only your API endpoints (so rendering of the page happens client-side, after all data fetching requests are done, which is slower).

To sum it up, Rainbow Rendering is faster, cheaper, simpler, and works for 99% of existing use cases.

I’ve even called this architecture the “Unicorn Architecture” because this upfront request processing server is like the horn of a unicorn. Every request goes in, and then the magic happens.

The Unicorn of Rainbow Rendering: an architecture to statically render anything, now made possible thanks to Next.js middlewares

Next.js offered no such solution out-of-the-box until recently. Next.js 12 (october 2021) has introduced middlewares and made Rainbow Rendering finally easy to setup.

Middlewares, a step toward Rainbow Rendering

Middlewares let you process the request before returning a page. Most common use case is authenticating the user, and redirecting toward a login page if needed.

In a middleware, you can rewrite an URL based on the request. It means that a request to “/my-page” with the cookie theme="dark" can be rewritten to “/dark/my-page”.

Note that a rewrite is slightly different from a redirect, because it won’t actually change the URL.

So in this example:

  • your folder structure is “/src/pages/[theme]/my-page.js”. “theme” is what we call a route parameter.
  • the user hits “/my-page” in their browser.
  • it will show the dark version of my-page, rendered at build-time using getStaticProps . The actual rendered page is “/dark/my-page”, but the URL stays “/my-page” in the browser.

The dark version of the page has been statically rendered, so it’s extremely fast and costs nothing. There is no server-render and no client-render either. Awesome!

The URL bloating nightmare

But what happens if in addition to the theme, you also want to setup multi-tenancy or A/B testing?

First option is to have a more complex page structure like this:

  • “/src/pages/ [theme]/[tenant]/[bucket]/my-page.js”

The getStaticPaths function of “my-page.js” might look like this:

export async function getStaticPaths() {  
return {
paths: [
{params:{ theme: "dark", tenant: "ACME", bucket: "A"}},
{params:{ theme: "dark", tenant: "ACME", bucket: "B"}},
{params:{ theme: "light", tenant: "ACME", bucket: "A"}},
{params:{ theme: "light", tenant: "ACME", bucket: "B"}},
]
}
}

It works, but the developer experience (DevX) is not crazy because it means you have to dig through a series of folder to reach your page.

Also, you might end up with very long URL. The end user won’t see this URL, but that’s still pretty ugly. You might even hit the usual 255 characters limitation for URLs.

Hopefully, there’s an alternative. Let me introduce [M], the Megaparam.

Resurrecting dead memes is my favorite leisure activity

Say hello to [M], the Megaparam

Who said you add to have one param per param? After all, this parameter won’t be visible to the end user. So you can do as you wish.

An alternate page structure would be :

  • “src/pages/[M]/my-page.js”

“M” or “Megaparam” is simply a combination of multiple parameters. There is no rules on how to build it. Possible examples are:

  • Using dashes: “M”==”light-ACME-B” to get the light theme, for company ACME and A/B testing bucket B. That’s the simplest pattern.
  • Using an encoding: “M”==”lXYZB”. First letter is the theme, “l” for “light”, “d” for “dark”. Letters 2 to 4 are the company id in our database, so “XYZ” for “ACME”. Letter 5 is the AB testing bucket, “B”.
  • Binary: “M”==”01111”. You can compute it using any existing encoding/decoding algorithm, based on the possible values for your params.

You can make the encoding as complex or as simple as you want, depending on your use case. This pattern is highly scalable in the number of parameters. You could handle A/B testing, multi-tenancy, theming, paid content etc. with only one Megaparameter.

From a technical standpoint, all you need to do is to design a function named “encode” that can build the Megaparam for a combination of parameters, and a function named “decode” that can read the Megaparam and split it into multiple parameters.

Finally, all variations of your page can be statically rendered: you avoid the cost of per-request server-side rendering, and you’re faster than client-side rendering. The Megaparam helps keeping the code clean.

Code demo

Because a good code sample says 1000 words, checkout our implementation:

https://github.com/eric-burel/megaparam-demo

You’ll find:

  • a file named “_middleware.ts”: that’s where you process the request to get parameters from the cookies, URL, headers…
  • a file named “megaparam-demo.tsx”, that’s your page
  • in a folder named “[M]”: that’s our Megaparam!

Here’s the end result. The light theme AND the dark theme are statically rendered, for the same page. A Next.js middleware picks the right one depending on a cookie. The URL stays clean, because the end user doesn’t actually see the Megaparam. It’s very “Mega”, and yet so discrete! :)

A demo of the Megaparam pattern, used to statically render 4 variations of the same page depending on the theme and user’s company (multi-tenancy)

Easy peasy, now you can statically render anything.

PS: you might be worried about build duration, because you need to pre-render many variations for each page.

But don’t worry: you have full control over the variations you build.

Ideally, you would render at build-time only the most popular variations (like Github.com in dark mode), and use fallback: "blocking" for the others.

If you add Incremental Static Regeneration to the mix, you may or may not create the most powerful static rendering pattern ever created. Who knows ¯\_(ツ)_/¯

Edit: discover our new follow-up article: “Treat your users right with HTTP Cache and Segmented Rendering” where we try to reproduce this pattern with only the standard HTTP cache.

Thanks for reading this article! If you liked it, please take a minute to discover our Next.js and GraphQL framework, VulcanNext.

Click to discover Vulcan Next, the Next.js GraphQL framework

Subscribe to Vulcan’s blog for more stories like this or follow me on Twitter: @ericbureltech

--

--

Eric Burel
VulcanJS

Next.js teacher and course writer. Co-maintainer of the State of JavaScript survey. Follow me to learn new Next.js tricks ✨ - https://nextjspatterns.com/