CogniPrep shows its prices in the visitor's currency. The pricing page and the comparison page both do it, and both get the number right in the first frame rather than correcting it afterwards.
The oldest piece of that machinery is a cookie set in proxy.ts, the file Next 16 renamed middleware to:
function setCountryCookie(response: NextResponse, request: NextRequest): NextResponse {
const country = request.headers.get('x-vercel-ip-country');
if (!country) return response;
response.cookies.set(COUNTRY_COOKIE, country.slice(0, 2).toUpperCase(), {
...LONG_LIVED_COOKIE_OPTIONS,
httpOnly: false,
maxAge: 60 * 60 * 24 * 30,
});
return response;
}
Two decisions in there are still right for reasons the comment above the function gets right. It is re-set on every request rather than only when missing, so a visitor who travels stops seeing the currency of the country they left. And thirty days rather than the year the device token gets, because this is a display preference and a stale one is refreshed by the next visit anyway.
The rest of that comment describes a system that no longer exists.
What the comment says
Paraphrasing: the pages that show prices are statically rendered, /pricing is on ISR, so they cannot read x-vercel-ip-country themselves without becoming per request. Handing the country to the client as a cookie lets a blocking script in app/layout.tsx pick the currency before anything is painted, with the cached HTML left untouched. And the cookie is deliberately not httpOnly, because that script has to read it.
Every clause of that was true when it was written. Here is the state of each one now.
/pricing is not on ISR:
export const dynamic = 'force-dynamic';
The blocking script in the root layout does not read the cookie. There is exactly one inline script in app/layout.tsx and it applies the dark mode class, which is a different flash to prevent.
And the currency is resolved on the server, in a module whose own doc block records the history:
/**
* ## Why this replaced the client-side approach
*
* The first version resolved the currency in the browser and swapped the price
* after the fact, first via React state, then via a pre-paint script plus CSS
* variants. Both flickered, and the CSS variant version was worse than that: the
* generated `<style>` was silently dropped from the production HTML, so the rules
* that hid the non-active currencies never shipped. Nothing caught it because a
* stale cached stylesheet masked the failure locally.
*/
export async function getDisplayCurrency(): Promise<DisplayCurrency> {
That is the mechanism the cookie was published for, and it was deleted. The pre-paint script went, the CSS variants went, the ISR cache on both price pages went with them. Per request rendering is the price paid, and it is cheap here because neither page touches the database.
So a reader of proxy.ts today is told that a cookie is not httpOnly so that a script can read it, when no script reads it.
The cookie is still load-bearing
It would be a tidy story if the cookie were dead. It is not. It has three server side readers, and the reason it survives is in none of them individually.
The currency resolver reads the header first and the cookie second:
const country =
headerList.get('x-vercel-ip-country') ?? (await cookies()).get(COUNTRY_COOKIE)?.value ?? null;
On Vercel that header is there on essentially every request, which makes the fallback look like belt and braces for local development. Then the Stripe side found the case where it is not.
We let Stripe act as merchant of record in a list of countries, and whether that is switched on for a checkout session is decided from the buyer's country. That function used to read the header alone, and its comment now explains what went wrong:
/**
* The two only disagree when the header is missing but the cookie is not, which
* is exactly the case Vercel produces when IP geolocation fails on a request
* from a visitor whose earlier request geolocated fine. The result was the one
* state we never want: an EU buyer quoted and charged in euros with Managed
* Payments off, so no VAT was collected on a sale that owes it.
*/
A header that is present on essentially every request is still a header that is absent sometimes, and sometimes it is absent on a checkout request rather than a page view. The fix was not to add a retry or a default. It was to make both resolvers answer the same way from the same two inputs in the same order, because the invariant that matters is not "the country is correct", it is "the country the price was quoted in is the country the tax routing used".
That function cannot call cookies() from next/headers, because it works from a Request. So it parses the raw header itself, and validates narrowly:
const value = part.slice(eq + 1).trim();
return /^[A-Za-z]{2}$/.test(value) ? value.toUpperCase() : null;
Two letters or nothing. The cookie is not httpOnly, so the page's own JavaScript can write it, and a reader that accepts whatever is in it is a reader that trusts the browser. The header still wins wherever it exists, which on Vercel is everywhere.
The third reader
The one nobody would predict from the cookie's name or its comment. Assessment centre exercises are written in British English and render in American spelling for American visitors, and the default comes from:
const country =
(await headers()).get('x-vercel-ip-country') ??
cookieStore.get(COUNTRY_COOKIE)?.value ??
null;
return country?.toUpperCase() === 'US' ? 'en-US' : 'en-GB';
Same header, same cookie, same order, a completely unrelated product decision. A chosen preference wins over both, so this only decides where a new visitor starts.
What I take from it
A comment that says what a thing does ages well. A comment that says why it exists ages badly, because the why is a claim about other code, and other code moves. This one is now a confident description of a design that was removed in two steps, attached to a function that is more important than it was when the comment was written.
The thing that saved us was not the comment. It was that a resolver which had to agree with another resolver carried the invariant in its own doc block, next to the sale that proved it.
If you want to see the per request version at work, open the pricing page and the figures are in your local currency, server rendered, with no swap after load. The practice those prices buy is per provider or per test format.
Top comments (0)