← Back to blog
Vibe codingScanaris Team · 6 min read · October 04, 2026

Security when deploying to Vercel

Vercel makes deploying so easy that it is easy to forget it is still a server on the internet. The platform itself is well protected: automatic TLS, global network, isolation between projects. The holes are the ones you put in the configuration yourself, and they are almost always the same five or six. This guide goes through them one by one with the command to check each and the exact change to close it, with apps generated with Cursor, Bolt or v0 that end up on Vercel in mind.

1. Environment variables that end up in the browser

On Vercel you define variables in the project dashboard, and the framework decides which ones reach the client. In Next.js, everything starting with NEXT_PUBLIC_; in Vite, with VITE_; in Nuxt, whatever sits under runtimeConfig.public. It is a useful convention until someone sets NEXT_PUBLIC_STRIPE_SECRET_KEY because the AI needed to access it from a component. That key travels in the bundle to every visitor.

Two habits prevent it. First, check the prefix: if a variable has the public prefix, assume it is public and ask yourself whether it should be. Second, use Vercel's environments (Production, Preview, Development) so production keys do not exist on test branches, and mark the sensitive ones as Sensitive, which prevents viewing them from the dashboard later. To check what reached the bundle:

curl -s https://yourdomain.com/ | grep -o '/_next/static/chunks/[^"]*\.js' | sort -u | head -20

Download those files and search for sk_live, service_role, AKIA or the names of your variables. More in secrets your app leaks in the browser.

2. Public preview deployments

Every branch and every pull request generates a URL like project-git-branch-team.vercel.app. It is handy for showing changes, and it is public: anyone with the URL sees the in-development version, with its half-finished features and, sometimes, its test data. Vercel adds a header by default so search engines do not index previews, but that does not stop a person from opening it.

The fix is in Settings, Deployment Protection: enable Vercel Authentication so only team members can access previews (available on the free plan), or password protection on paid plans. And remember that the production project.vercel.app URL also exists alongside your domain: redirect it to your domain or set a canonical so it does not compete with you on Google.

3. Security headers nobody set

Vercel serves your app with the minimum headers; the security ones you have to add yourself. Without them, your site can be embedded in someone else's iframe, the browser guesses content types and there is no policy limiting which scripts run. In vercel.json they are added like this:

{
  "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 you can also do it from next.config.js with the headers() function. The Content-Security-Policy deserves a separate step because it has to be tuned to your scripts; each header is explained in HTTP security headers explained for humans, and you can check yours with the headers analyzer.

4. Published source maps

Source maps let you see the original code instead of the minified one. They are useful for debugging and a bad idea in production: they give away your logic, your internal routes and the comments saying "TODO: remove this key". Next.js disables them in the browser by default (productionBrowserSourceMaps: false); in Vite it depends on build.sourcemap, which some templates set to true. Check whether they exist:

curl -sI https://yourdomain.com/_next/static/chunks/main-abc123.js.map | head -1

If it answers 200, they are published. If you need maps for error tracking, upload them to your monitoring service at build time and do not serve them to the public.

5. Files and routes that should not answer

Everything in the public/ folder is served as is. A .env copied there by mistake, a backup.json, a config.yml with credentials: public. API routes count too: /api/debug, /api/seed or /api/admin that the AI created for testing and that are still in production without authentication. And cron routes: Vercel calls them with an Authorization header containing your CRON_SECRET; if you do not check it in the code, anyone can trigger your scheduled task.

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://yourdomain.com/$p"
done

Anything that answers 200 deserves a look.

6. Functions without limits

An API route that calls OpenAI or sends emails without limiting requests is a bill waiting to be discovered. Vercel has a firewall with per-IP rate limit rules and a challenge mode against bots that you enable from the dashboard without touching code. For endpoints that cost money, also add a per-user limit in your own logic and require a session.

7. CORS and cookies on API routes

If your API answers with Access-Control-Allow-Origin: * and also uses cookies, any website can make requests on behalf of your users. On Vercel it is configured per route in vercel.json or in the handler itself. List the allowed origins explicitly and never combine the wildcard with credentials. Detail in misconfigured CORS.

8. What Vercel already does for you

So you do not waste time: the TLS certificate is issued and renewed on its own, HTTP redirects to HTTPS automatically, functions run isolated and deployment logs are audited. What it does not do is decide for you what is public, which headers you send or who can see your previews. That part is yours.

Five-minute checklist

  • No variable with a public prefix contains a secret key.
  • Deployment Protection enabled for previews.
  • Security headers in vercel.json or next.config.js.
  • Source maps not accessible in production.
  • Nothing sensitive in public/; test routes removed; cron protected with its secret.
  • Rate limit on routes that cost money.
  • CORS with explicit origins.
  • The .vercel.app URL redirects to your domain.

Scanaris checks it for you

Scanaris reviews your Vercel deployment from outside: headers, secrets in the bundle, source maps, exposed files and routes, CORS, cookies and TLS, and gives you a fix prompt for each failure to paste into Cursor, Bolt or v0 with the exact change in vercel.json or in your code. These are read-only checks on what is already public; it never touches your project. The platform-specific risks are in the Vercel security guide, and if your app is Next.js, in the Next.js checklist.

Frequently asked questions

Are Vercel previews private by default?

No. They are public to anyone with the URL, even though they are not indexed by search engines. Enable Vercel Authentication under Deployment Protection to limit them to your team.

Does Vercel set security headers automatically?

Only the basic operational ones. HSTS, X-Frame-Options, Content-Security-Policy and the rest you have to configure yourself in vercel.json or in the framework.

What if a secret key was already published with a public prefix?

Treat it as compromised even if you remove it from the code: the previous bundle may be cached or downloaded. Rotate the key at the source service, move it to a variable without the prefix and deploy again.

How does your site do?

Run Scanaris and check it in 30 seconds, free.

Scan my site

Related checks

Security guides for your stack