Full disclosure, I hate frameworks of frameworks. How does a thing become so large that it needs other large things to prop it up? Especially if the scaffolding is opaque, and you can’t fathom why it does what it does. People complain about Webpack, but at least you could reason about the pipeline end-to-end.

I’ve written UI apps in React since before the first major version was released. Things like a rich text editor, a photo-editor, some games etc. Back in my day, things had lifecycle methods.. And we injected an instance of V8 inside Laravel to SSR before it was mainstream.

I like React, it’s been straightforward to use. There are faster/lighter/easier things out there, some more interesting like this. I don’t really want to talk about them.

I despise NextJS, not for it’s own faults although I can talk about those too, but for the bullshit surrounding it. From Guillermo’s annoying tweets to the npm package name grab, to two bilboards on the same street corner double-vercel to Youtube videos titled “Cool FramerJS animations with NextJS”. Bro it’s an SSR framework why do you need to… ugh.

Anyway, I recently discovered that NextJS 15 forces some sync APIs like useParams to be async. It reminded me of a discourse from a few years ago about function coloring.

TLDR; NextJS needs to know which components to skip when pre-rendering, async is how they do it. GGPO.

What the hell is component coloring?

If you’re familiar with async/await you’d remember that to use the output of an awaited function you have two choices:

  • make the caller async
  • or slap a .then(callback)

The second option isn’t really “using the awaited value” as things are out of the caller’s hands now and the awaited value can only be used in the callback.

The first option is what the cool kids are doing. And this “making caller async” can potentially go all the way to your root component. Good luck.

But what does this have to do with server components? I’m glad you asked.

NextJS pre-renders components. Which means it’ll render some components before a user even asks for them. Eg: some static stuff that doesn’t depend on user behavior. However evaluating the contents of an entire component to determine whether it can be statically rendered is costly.

Remember the stupid annotations “use client”? The thing that everyone slaps at the top of every component so they can keep writing React code the way they already liked. That’s how React sorts your client/isomorphic components into neat little buckets.

However that’s not good enough, you need even more fine grained buckets to make sure you’re not wasting compute evaluating the contents of a component that’s dependent on indeterminate data.

In a recent update NextJS decided to make a few APIs asynchronous that actually are not. Think of it as an async IIFE, basically Promise.resolve(value).

What in the world?

The reason for doing this is to force the developers to somehow color their components, which makes it easier for NextJS to determine where to draw the pre-rendering boundary.

An example of this is the useParams API.

// app/greeting/page.jsx
const BlueComponent = () => {
  return <div>Hello!</div>
}

// NextJS 14
// app/greeting/[:username]/page.jsx
const AnotherBlueComponent = () => {
  const { username } = useParams();

  if (!username) {
    <BlueComponent />
  }

  return <div>Hello! {username}</div>
}

As the parameters come from a request, NextJS would have to see into the future to pre-render this. But as brilliant as Vercel is at marketing, they can’t really pre-render something that’s dynamic. Why? Coz it could literally be anything, like social security numbers, or the phone number of your high school crush.

So to be able to efficiently draw a boundary around the work that NextJS needs to do around SSR they want you to declare which components are dependent on these indeterminate parameters.

But how can a framework be sure that developers will do the right categorization? You could either do something like introduce a new keyword that developers would have to use to define which components our dynamically rendered like “server pre-render pls” and “server skip” or you could use something that already exists in the language. JavaScript already has a keyword that could be helpful, but a framework can’t still reliably ensure that a user will declare their components asynchronous. So by making a handful crucial APIs asynchronous NextJS can effectively force the developers to color their components. If a component is blue, i.e. synchronous NextJS can pre-render it but if a component is green, i.e. asynchronous NextJS can reliably skip without leaving perf gains on the table.

// app/greeting/page.jsx
const BlueComponent = () => {
  return <div>Hello!</div>
}

// NextJS 15
// app/greeting/[:username]/page.jsx
const GreenComponent = async () => {
  const { username } = await useParams();
  return <div>Hello! {username}</div>
}

Theo will tell you that these greens (components) are good for you. I will tell you that using existing language feature to improve a framework is good but selling your soul to SSR for the “perceived” speed improvement is not worth it. You’re only writing a client-side Framer animation for a marketing website anyway!

If you love NextJS and really disagree with everything I mention, LMK in the comments below.