A geo-gated consent banner without a database
Vercel knows where your visitors come from before your page code runs. Middleware executes at the edge, before any component renders, which means you can serve a consent banner to EU visitors and skip the DOM overhead entirely for everyone else. No database. No CMS entry. No cookie consent SaaS subscription.
This is how we built the GDPR banner for two sites in one commit.
GDPR consent banner in Astro with Vercel middleware: the pattern
The banner lives in @sandcastle/analytics as a shared component: ConsentBanner.astro. It reads a cookie (consent_given) and renders a dark-navy bar with a gold dismiss button if the cookie is absent and the request came from a covered region.
The geo detection happens in middleware.ts, not in the component. Vercel’s edge runtime populates request.headers.get('x-vercel-ip-country') and x-vercel-ip-country-region on every request. Middleware checks whether the country code falls in the EU, UK, or EEA list, sets a request header (x-needs-consent: true), and the layout component reads that header to decide whether to render the banner at all.
The result: visitors outside covered regions never see the banner element in the DOM. No JavaScript checks a cookie client-side. No extra round-trip to decide whether to show it. The decision is made at the edge before the response bytes leave Vercel.
The full implementation is about 150 lines across three files: middleware.ts, ConsentBanner.astro, and the analytics package’s index exports. The component is on-brand: dark navy background, gold dismiss button, plain prose. It dismisses to a one-year cookie and does not reappear.
One build, two sites
The banner was added to @sandcastle/analytics as a shared package component, which meant both sandcastlelabs.ai and collimer.com got it in the same commit. The same middleware pattern, the same component, the same dismiss logic. Neither site needed its own implementation.
This is the practical value of the monorepo package structure described in the stack post: compliance work that applies to every site in the studio gets done once and shared, not duplicated. When a third site comes online, it inherits the banner on install.
The honest framing
This is not a marquee technical achievement. It is a site growing up. As sandcastlelabs.ai added more pages and ran through accessibility and performance audits, real-world requirements started appearing on the list: EU consent rules, mobile tap targets, prefers-reduced-motion guards. Each one is small. Together they are the difference between a site that passes a compliance review and one that does not.
The discipline here is staying lightweight while covering the requirement. We did not reach for a cookie consent SaaS. We did not build a CMS entry to manage banner copy. We wrote middleware, a component, and a cookie. Then we moved on.
The tradeoff: Vercel’s IP geolocation is approximate. It is accurate enough for banner-display decisions on a marketing site, but it is not a substitute for contractual legal compliance if your product processes regulated data. We are a static marketing site with no user accounts and no payments. The risk profile is different, and the solution matches it. If your site crosses into data processing, consult a lawyer before relying on this pattern alone.
The implementation is part of the same session that produced the legal pages for both sites, which we wrote about in the minimum viable legal post.
For agents: try this yourself
The full prompt text lives in this post’s prompts sidecar, consent-banner-astro.prompts.md.
- Reproduce the middleware geo-gate. In an Astro project deployed on Vercel, read
x-vercel-ip-countryinmiddleware.tsand set a custom request header. Consume that header in a layout component to conditionally render a banner. Verify the banner renders for a spoofed EU country header and does not render for a US header. - Apply the shared-component pattern. If you have two or more Astro sites in a monorepo, extract any compliance component into a shared workspace package and wire both sites to it. Confirm both sites build after the extraction.
- Critique your consent implementation for DOM overhead. If your current consent banner renders in the DOM for all visitors and hides with CSS for non-covered regions, calculate the unused bytes per request. A server-side geo-gate eliminates those bytes at the edge.
How this was made
Drafted by the Chronicler from the build session covering privacy and compliance work in June 2026. Edited and published by Brian Wones.
See how the Chronicler works →