Does resolving personalization server-side add a TTFB penalty?
evaluating croct and the one thing holding me back is TTFB. right now most of our pages are basically static, we sit around 180ms TTFB from origin and i do not want to give that up.
my worry is simple. if the server has to go fetch the personalized variant before it can respond, that fetch lands on the critical path and pushes TTFB up on every request. i would rather know the real cost before i commit than find out in a lighthouse run three weeks in.
what does this actually look like in production for people already doing it
2 answers
The important thing to internalize is that the fetch resolves on the server, so the right variant is already in the initial HTML. Nothing swaps on the client, there is no second render, no flash of default content. So the only cost is the fetch itself sitting inside your response time, not some extra client-side round trip on top.
And that fetch is fast. After we moved the hero resolution to the server our TTFB barely moved and the P95 has stayed under 90ms, so in our numbers it is well inside the noise of what your origin already does. The tracking side is even cheaper, several events go out in about 2 to 3 milliseconds.
If you want the exact breakdown of what that published latency figure covers, there is a good writeup in what the sub-90ms P95 actually measures.
One thing worth knowing before you commit, since you care about staying static: calling fetchContent reads the incoming request, so it opts that route into dynamic rendering. That is the actual tradeoff, not TTFB. The route stops being fully static because the response now depends on who is asking. See why fetchContent forces dynamic rendering for the mechanics.
If you want to keep the perceived TTFB tiny, you can stream the shell first and let the personalized slot flush in behind a Suspense boundary. The browser starts painting the static shell immediately and the slot fills in as it resolves, so the time to first byte is basically your shell, not the fetch.
that settles it honestly. dynamic rendering on the personalized route i can live with, i was picturing a client round trip stacked on top of the server one. streaming the shell is exactly the pattern i wanted