Vercel maakt deployen zo makkelijk dat je vergeet dat het nog steeds een server op internet is. Het platform zelf is goed beschermd: automatische TLS, wereldwijd netwerk, isolatie tussen projecten. De gaten zet je zelf in de configuratie, en het zijn bijna altijd dezelfde vijf of zes. Deze gids loopt ze een voor een door met het commando om ze te controleren en de exacte wijziging om ze te dichten, met het oog op apps die met Cursor, Bolt of v0 zijn gegenereerd en op Vercel belanden.
1. Omgevingsvariabelen die in de browser belanden
Op Vercel definieer je de variabelen in het projectpaneel, en het framework bepaalt welke bij de client aankomen. In Next.js alles wat begint met NEXT_PUBLIC_; in Vite met VITE_; in Nuxt wat onder runtimeConfig.public staat. Een nuttige conventie totdat iemand NEXT_PUBLIC_STRIPE_SECRET_KEY zet omdat de AI er vanuit een component bij moest. Die sleutel reist in de bundle naar alle bezoekers.
Twee gewoontes voorkomen het. Ten eerste het voorvoegsel controleren: heeft een variabele het publieke voorvoegsel, ga er dan van uit dat ze publiek is en vraag je af of dat zo hoort. Ten tweede de omgevingen van Vercel gebruiken (Production, Preview, Development) zodat productiesleutels niet bestaan in testbranches, en gevoelige markeren als Sensitive, waardoor je ze later niet meer in het paneel kunt zien. Om te controleren wat in de bundle terecht is gekomen:
curl -s https://jouwdomein.com/ | grep -o '/_next/static/chunks/[^"]*\.js' | sort -u | head -20
Download die bestanden en zoek naar sk_live, service_role, AKIA of de naam van je variabelen. Meer in geheimen die je app in de browser lekt.
2. Publieke preview-deployments
Elke branch en elke pull request genereert een URL als project-git-branch-team.vercel.app. Handig om wijzigingen te laten zien, en publiek: iedereen met de URL ziet de ontwikkelversie, met half afgemaakte functies en soms testdata. Vercel voegt standaard een header toe zodat zoekmachines previews niet indexeren, maar dat weerhoudt een persoon er niet van om hem te openen.
De oplossing zit in Settings, Deployment Protection: schakel Vercel Authentication in zodat alleen teamleden bij de previews kunnen (beschikbaar in het gratis plan), of wachtwoordbeveiliging in de betaalde plannen. En onthoud dat de productie-URL project.vercel.app ook bestaat naast je domein: stuur hem door naar je domein of zet een canonical zodat hij niet met je concurreert op Google.
3. Beveiligingsheaders die niemand heeft gezet
Vercel serveert je app met de minimale headers; de beveiligingsheaders moet je zelf toevoegen. Zonder kan je site in een vreemde iframe worden ingebed, raadt de browser contenttypes en is er geen beleid dat beperkt welke scripts draaien. In vercel.json voeg je ze zo toe:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}
In Next.js kan het ook vanuit next.config.js met de functie headers(). De Content-Security-Policy verdient een aparte stap omdat hij op je scripts moet worden afgestemd; de uitleg van elke header staat in HTTP-beveiligingsheaders uitgelegd voor mensen, en je controleert de jouwe met de headeranalyzer.
4. Gepubliceerde source maps
Source maps laten de originele code zien in plaats van de geminificeerde. Handig om te debuggen en een slecht idee in productie: ze geven je logica, je interne routes en de opmerkingen met "TODO: deze sleutel weghalen" weg. Next.js schakelt ze standaard uit in de browser (productionBrowserSourceMaps: false); in Vite hangt het af van build.sourcemap, dat sommige templates op true zetten. Controleer of ze bestaan:
curl -sI https://jouwdomein.com/_next/static/chunks/main-abc123.js.map | head -1
Antwoordt het 200, dan zijn ze gepubliceerd. Heb je maps nodig om fouten te traceren, upload ze dan bij de build naar je monitoringdienst en serveer ze niet aan het publiek.
5. Bestanden en routes die niet zouden moeten antwoorden
Alles in de map public/ wordt ongewijzigd geserveerd. Een per ongeluk daarheen gekopieerde .env, een backup.json, een config.yml met inloggegevens: publiek. API-routes tellen ook mee: /api/debug, /api/seed of /api/admin die de AI aanmaakte om te testen en die zonder authenticatie in productie zijn gebleven. En de cron-routes: Vercel roept ze aan met een Authorization-header die je CRON_SECRET bevat; controleer je die niet in de code, dan kan iedereen je geplande taak starten.
for p in .env .env.local .git/config config.json backup.zip api/debug api/seed; do
printf "%s -> " "$p"; curl -s -o /dev/null -w "%{http_code}\n" "https://jouwdomein.com/$p"
done
Alles wat 200 antwoordt, verdient een blik.
6. Functies zonder limieten
Een API-route die OpenAI aanroept of e-mails verstuurt zonder verzoeken te beperken, is een rekening die wacht om ontdekt te worden. Vercel heeft een firewall met rate-limitregels per IP en een challenge-modus tegen bots die je vanuit het paneel inschakelt zonder code aan te raken. Voeg voor endpoints die geld kosten ook een limiet per gebruiker toe in je eigen logica en eis een sessie.
7. CORS en cookies op API-routes
Antwoordt je API met Access-Control-Allow-Origin: * en gebruikt ze bovendien cookies, dan kan elke website verzoeken doen namens je gebruikers. Op Vercel configureer je dit per route in vercel.json of in de handler zelf. Som de toegestane origins expliciet op en combineer de wildcard nooit met credentials. Details in verkeerd geconfigureerde CORS.
8. Wat Vercel al voor je doet
Zodat je geen tijd verliest: het TLS-certificaat wordt automatisch uitgegeven en vernieuwd, HTTP stuurt automatisch door naar HTTPS, functies draaien geïsoleerd en de deploymentlogs worden bijgehouden. Wat het niet doet, is voor jou beslissen wat publiek is, welke headers je stuurt of wie je previews mag zien. Dat deel is van jou.
Checklist van vijf minuten
- Geen enkele variabele met publiek voorvoegsel bevat een geheime sleutel.
- Deployment Protection ingeschakeld voor previews.
- Beveiligingsheaders in
vercel.jsonofnext.config.js. - Source maps niet toegankelijk in productie.
- Niets gevoeligs in
public/; testroutes verwijderd; cron beschermd met zijn secret. - Rate limit op routes die geld kosten.
- CORS met expliciete origins.
- De
.vercel.app-URL stuurt door naar je domein.
Scanaris controleert het voor je
Scanaris controleert je Vercel-deployment van buitenaf: headers, geheimen in de bundle, source maps, blootgestelde bestanden en routes, CORS, cookies en TLS, en geeft je per fout een fix prompt om in Cursor, Bolt of v0 te plakken met de exacte wijziging in vercel.json of in je code. Het zijn alleen-lezen controles op wat al publiek is; je project wordt nooit aangeraakt. De platformspecifieke risico's staan in de beveiligingsgids voor Vercel, en is je app Next.js, dan in de Next.js-checklist.
Veelgestelde vragen
Zijn de previews van Vercel standaard privé?
Nee. Ze zijn publiek voor wie de URL heeft, ook al worden ze niet geïndexeerd door zoekmachines. Schakel Vercel Authentication in onder Deployment Protection om ze tot je team te beperken.
Zet Vercel automatisch beveiligingsheaders?
Alleen de basale operationele headers. HSTS, X-Frame-Options, Content-Security-Policy en de rest moet je zelf configureren in vercel.json of in het framework.
Wat als een geheime sleutel al met publiek voorvoegsel is gepubliceerd?
Beschouw hem als gecompromitteerd, ook als je hem uit de code haalt: de vorige bundle kan gecachet of gedownload zijn. Roteer de sleutel bij de brondienst, verplaats hem naar een variabele zonder voorvoegsel en deploy opnieuw.