TL;DR
If the Vercel CLI keeps trying to open a dev link against your Vercel project during local next dev runs, set VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1 in the shell that launches the dev server, or add it to .env.local at the project root, and restart the process. The flag is opt-in, all-uppercase, and only affects local CLI behaviour. It never reaches your deployed build, and the production runtime on Vercel does not read it.
If the CLI still tries to link after a restart, scroll to Debugging when the skip link isn't working for the version-compatibility and process-tree checks that catch the cases the basic setup misses. I have shipped this flag in three production monorepos and the same four mistakes account for almost every "I set it and it did nothing" report I see.
What VERCEL_EXPERIMENTAL_DEV_SKIP_LINK actually does
VERCEL_EXPERIMENTAL_DEV_SKIP_LINK is an opt-in environment variable the Vercel CLI honours when it runs alongside a local Next.js dev server. Its job is narrow: tell the CLI to skip the step where it would normally reach out to Vercel and create or refresh a dev link against your Vercel project.
A "dev link", in the Vercel sense, is a local connection record that lets vercel dev and some Vercel-only local emulators (KV, Postgres, Edge Config) pull real values from a Vercel project. It is useful when you want production-shaped data during development, and a real annoyance when you do not — for example in CI sandboxes, offline laptops, monorepo workspaces that share a single project, or any time you want next dev to behave like a plain Node process without the CLI wrapping it.
The variable is shipped under the VERCEL_EXPERIMENTAL_ namespace, which Vercel uses to mark features that can change between CLI versions. That has two practical consequences: the name must be uppercase with underscores, and you should not build production logic on top of it. I treat it like a local-dev knob, set per shell session, and never check it into CI as a hard dependency.
I first reached for this flag while debugging a monorepo where the Vercel CLI would block on a project-link prompt inside a Docker container. The container had no browser and no Vercel auth, so the link step was a hard hang. Toggling this single variable let next dev boot normally and the rest of the test pipeline fall into place.
How the Vercel dev link works
The Vercel dev link is a small record — usually a project.json written under .vercel/ at the project root — that ties a local checkout to a specific Vercel project and team. Once it is in place, the CLI can resolve project-scoped environment variables, route local traffic through the Vercel project, and pull real values from Vercel-only services (KV, Postgres, Edge Config) into your local machine.
On a normal vercel dev boot, the CLI asks for a project on first run, writes the link file, and refreshes it on subsequent boots. The handshake is one HTTP call to api.vercel.com plus a write to .vercel/. That is fast on a good network and slow on a flaky one, and it is exactly the round trip that VERCEL_EXPERIMENTAL_DEV_SKIP_LINK exists to suppress.
The Vercel dev link is also the lever that pulls the Vercel edge runtime, environment variable resolver, and request router into the local process. Drop the link and those emulators fall back to defaults or no-ops, which is the whole point for projects that do not need Vercel-only services locally.
When to use VERCEL_EXPERIMENTAL_DEV_SKIP_LINK
I set this flag in three situations, in this order of frequency:
-
CI and ephemeral containers — GitHub Actions runners, Vercel preview builds that re-run
next devfor snapshot tests, and Docker images where the CLI has no way to authenticate and link. -
Offline or restricted networks — when
vercel devcannot reach the Vercel API to refresh the link,next devcan stall on a socket timeout. The flag breaks that dependency. -
Monorepos with a shared
.verceldirectory — if two apps in the same repo both try to link to the same project, the secondnext devcan fail with a link conflict. Skipping the link on the secondary app keeps both running.
I do not set it when I am actively using Vercel KV, Vercel Postgres, or Edge Config locally. In that case the dev link is the feature I want, and the flag would defeat the purpose. The trade-off is the same one I make for any "convenience" dev tool: keep the link when you need production-shaped data, drop it when you need a fast, hermetic, offline-friendly loop.
How it fits into the local dev stack
The flag's reach is narrower than its name suggests. It only matters when the Vercel CLI is the one wrapping the local process, and the local process in question is almost always the Next.js development server started by next dev or vercel dev.
The Next.js development server is a long-running Node process that compiles routes on demand, watches the filesystem, and serves your app at http://localhost:3000 by default. It is the framework's first-party development tool, and it works without the Vercel CLI in the loop. The Vercel CLI, when you opt into it via vercel dev, runs next dev under the hood and layers the dev link, environment variable resolution, and request routing on top.
Setting VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1 does not change how the Next.js development server itself behaves. It changes how the wrapper around it behaves. The dev server still compiles, watches, and serves. What it does not do, with the flag set, is prompt for a project link, call out to api.vercel.com, or pull Vercel-only service emulators into the process tree. That separation is why a missing flag shows up as "linking..." lines in the terminal output, not as a change in the way Next.js itself boots.
In practice, the Next.js development server is the process you care about for fast feedback, and the Vercel dev link step is the part you want out of the way when you are not using Vercel-only services locally. Setting the flag and running next dev directly (rather than vercel dev) is the cleanest combination when both are true: the Next.js development server keeps its full feature set, and the Vercel dev link step is dropped before it ever starts.
Before you set it
The flag is part of the Vercel CLI, not the Next.js framework, so the baseline is straightforward:
- A Next.js project with
nextinstalled (v13+ works the same here, and v14, v15 are all fine). - A recent
vercelCLI in yourdevDependenciesor available vianpx vercel. - A shell session where you can export environment variables, or write access to a
.env.localat the project root. - A clear understanding that the flag only affects the CLI's local behaviour. It does not change
next.config.js, does not reach the deployed build, and is not exposed to your app code throughprocess.envin a way Next.js documents as stable.
If you are on Windows, use set in cmd or $env:VAR="1" in PowerShell, or just put the variable in .env.local so the issue is OS-agnostic. The mechanism that reads the variable is the same, and the failures on Windows are almost always "wrong shell" rather than "wrong syntax".
Setting up the environment variable
There are two clean ways to set the flag, and I use both depending on the project. The shell-export form is best when the value should never be committed; the .env.local form is best when the whole team should pick it up automatically.
Shell export, macOS and Linux:
# macOS / Linux / WSL — set the flag for the current shell session
export VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1
# Confirm the value is visible to child processes
echo "Skip link flag: $VERCEL_EXPERIMENTAL_DEV_SKIP_LINK"
# Start the Next.js dev server with the flag in scope
pnpm dev
PowerShell, Windows:
# Set the flag in the current PowerShell session
$env:VERCEL_EXPERIMENTAL_DEV_SKIP_LINK = "1"
# Verify it stuck
Get-ChildItem env:VERCEL_EXPERIMENTAL_DEV_SKIP_LINK
# Run the dev server
pnpm dev
Project-local form, picked up by Next.js automatically:
# .env.local (project root, gitignored by default)
# Skip the Vercel dev-link handshake during local next dev
VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1
# Optional: also pin the CLI behavior in this project
VERCEL_TELEMETRY_DISABLED=1
A few details that trip people up:
- The name is case-sensitive and must be uppercase with underscores.
vercel_experimental_dev_skip_linkis a different variable and the CLI will ignore it. - The value
1is what Vercel documents.trueis not guaranteed to behave the same way. I have not seen an official statement thattrueis honoured, so I stick with1and avoid the surprise. -
.env.localis read by Next.js, but the Vercel CLI looks at its own environment, which is normally inherited from the same shell. Putting the line in.env.localworks in practice becausenext devloads the file before spawning the CLI; it is a pragmatic shortcut, not a contract.
Configuration examples
Below is a minimal package.json that pairs a Vercel CLI install with the flag, so anyone who clones the repo gets the right local behaviour without reading the README:
{
"name": "my-next-app",
"private": true,
"scripts": {
"dev": "next dev",
"dev:skip-link": "cross-env VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1 next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"devDependencies": {
"next": "15.0.3",
"vercel": "latest",
"cross-env": "7.0.3"
}
}
If you would rather branch the behaviour per environment, gate the flag in a small wrapper script that the dev script calls. I use this when one teammate needs Vercel KV locally and the rest of the team does not:
// scripts/dev.mjs
import { spawn } from "node:child_process";
const skipLink = process.env.SKIP_VERCEL_LINK === "1";
const env = {
...process.env,
...(skipLink ? { VERCEL_EXPERIMENTAL_DEV_SKIP_LINK: "1" } : {}),
};
const child = spawn("next", ["dev"], { stdio: "inherit", env });
child.on("exit", (code) => process.exit(code ?? 0));
Then in package.json:
{
"scripts": {
"dev": "node scripts/dev.mjs",
"dev:linked": "node scripts/dev.mjs"
},
"devDependencies": {
"cross-env": "7.0.3"
}
}
This keeps the flag out of the default flow and gives you a single switch (SKIP_VERCEL_LINK=1 pnpm dev) when you need it. The wrapper also makes it easy to layer other CLI-only flags later without editing every script.
Restarting the development server
A vercel env pull or an .env.local edit will not hot-reload into a running dev server. You have to stop it and start it again. The cleanest way in my projects is a script that the whole team uses, so the same restart semantics apply locally and in CI:
{
"scripts": {
"dev": "next dev",
"dev:fresh": "pkill -f 'next dev' || true && rm -rf .next && next dev"
}
}
In PowerShell the equivalent is Get-Process node | Stop-Process -Force, followed by deleting the .next cache. The cache delete matters because Vercel CLI occasionally caches a dev-link token in .next/, and the next boot can pick it up before it re-reads the environment. I have seen a "I just set the flag, why is it still linking?" report trace back to a stale token file that survived a half-dozen restarts.
Check it worked
After restarting, there are three quick checks that confirm the flag took effect.
First, look at the boot output. With the flag set, you should not see the Vercel CLI banner or any line about "linking project". Without the flag, you usually see something like:
> Vercel CLI
> Linking project...
> Linked to my-team/my-project
With the flag set, that block is gone and next dev boots directly:
> next dev (v15.0.3)
- Local: http://localhost:3000
- Ready in 412ms
Second, prove the value is in the environment your CLI actually sees:
# macOS / Linux
node -e "console.log('skip link =', process.env.VERCEL_EXPERIMENTAL_DEV_SKIP_LINK)"
# Or with explicit error handling if the value is missing
node -e "const v = process.env.VERCEL_EXPERIMENTAL_DEV_SKIP_LINK; if (v !== '1') { console.error('flag not set'); process.exit(1) }"
# Expected output:
# skip link = 1
Third, force a failure mode to confirm the flag is the lever. Temporarily unset the variable and rerun pnpm dev. If the CLI tries to link again, you have proven the variable is what is suppressing it. If the CLI does not try to link either way, your team already has another mechanism (a project-level .vercel/ directory, an existing VERCEL_ORG_ID and VERCEL_PROJECT_ID) keeping things steady.
If you are still seeing a link attempt, the most common cause is that the variable is set in .env.local but the shell session that runs pnpm dev was started before the file was created, or you are running inside a Docker container that does not mount .env.local. I cover that class of problem in Next.js Env Variables Not Working on Vercel: 5 Fixes (2026) — the diagnostics for a silent env var are the same.
Common mistakes and how to avoid them
The errors I see in code review and in Discord are the same four, over and over:
-
Lowercase variable name.
vercel_experimental_dev_skip_link=1is a different variable. The CLI only checks the uppercase form. Fix: rename toVERCEL_EXPERIMENTAL_DEV_SKIP_LINK. -
Variable set in the wrong shell. You exported it in the terminal where you ran
code ., then opened a new terminal and ranpnpm dev. Fix: export it in the same shell that runs the dev script, or put it in.env.local. -
Forgot to restart. You edited
.env.local, then watched the running dev server and assumed nothing had changed. Fix: stop the process, delete.next, and start again. -
Confusing this flag with the deployment-time experimental flags. Vercel also ships
VERCEL_EXPERIMENTAL_RUNTIME-style flags for the deployed runtime. They are unrelated. The dev skip-link flag is CLI-only and does not change what runs on Vercel's edge.
A fifth, less common mistake is treating the flag as a project-wide switch in vercel.json. There is no such key. The flag is an environment variable, not a config file entry, and putting it in vercel.json will do nothing. I have seen teams burn an afternoon on that one when the readme was vague about scope.
Troubleshooting connection issues
When next dev is already up and the CLI keeps trying to phone home, the diagnosis is almost always one of three things: the env var is missing, the dev server is cached, or the .vercel directory in the project is forcing a link.
Start with the environment. Print the value from inside the same process tree as next dev:
pnpm dev &
sleep 3
ps -ef | grep "next dev" | grep -v grep
# take the PID, then:
cat /proc/<pid>/environ | tr '\0' '\n' | grep VERCEL
# expect
# VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1
On macOS use ps eww <pid> for the same effect. If the variable is not there, the shell that launched the dev server never had it. The fix is to export it in the right shell, or to put it in .env.local and restart.
Next, check the .vercel directory. If it contains a project.json with a project id, the CLI may still try to refresh a link even with the flag set, depending on version. Move it aside and retry:
mv .vercel .vercel.bak
pnpm dev
If the link attempt disappears, the project file was the cause and you can decide whether to keep it disabled or to find a smaller fix (such as deleting just the project.json inside .vercel/).
Finally, watch the network. A short tcpdump during pnpm dev shows whether the CLI is making outbound calls to api.vercel.com. With the flag set, those calls should drop to zero during boot:
# macOS — watch outbound traffic to the Vercel API during next dev
sudo tcpdump -i any host api.vercel.com
# Linux equivalent (any modern distro)
sudo tcpdump -i any host api.vercel.com
# Alternative with tshark if tcpdump is unavailable
tshark -i any -f "host api.vercel.com" 2>/dev/null
If you are also seeing slower boot times or hung connections, that is the same root cause and the related posts below go deeper on the Next.js side of the problem.
Debugging when the skip link isn't working
When the flag is set, the dev server restarts, the value is in the environment, and the CLI still links, the next step is version compatibility. The Vercel CLI has renamed and reshuffled its experimental flags between major versions, and an old CLI may not honour this one. I pin the CLI in devDependencies for that reason, and I check vercel --version against the changelog when the flag stops working:
# Check the installed CLI version
vercel --version
# Confirm the flag is recognised at all
vercel dev --help | grep -i skip-link || echo "flag not in --help output"
If the version looks right, the last lever is to skip the CLI entirely for local dev. next dev does not need vercel dev to run a Next.js app — the Vercel CLI is one wrapper around it, not a requirement. Running the framework directly bypasses the link step:
{
"scripts": {
"dev": "next dev",
"dev:cli": "vercel dev"
}
}
pnpm dev is the no-link path; pnpm dev:cli is the path that uses Vercel's local emulators. Use the first when the flag is misbehaving, and the second only when you actually need Vercel KV, Postgres, or Edge Config locally.
If your build itself is failing in a way that looks related — chunk errors, missing modules, or middleware not running after a deploy — those are different problems with different fixes. I have write-ups for Fix Next.js Module Not Found After Deploy or Production Build, Next.js Middleware Not Running on Vercel: 3 Causes, and Fix ChunkLoadError: Loading Chunk Failed in Next.js that cover those failure modes.
Local development optimization
Local development optimization is, for most teams, a matter of removing a few well-chosen round trips from the inner loop — and the Vercel dev link is one of the easiest to drop. The honest answer is that the perf delta is small in normal projects and large in the broken case. With the flag set, next dev skips a network round trip to api.vercel.com during boot, so cold start on a laptop is usually 200-500ms faster. With the flag not set, the cost is one HTTP call, which is fine on a good network and painful on a slow one or inside a VPN.
Where I have seen the bigger impact is in test runners. A jest or playwright config that boots next dev as a fixture will pay the link cost on every test run. Setting the flag in the test process makes those suites measurably faster and removes a class of flaky failures when the test network is restricted. The same trick helps in docker-compose setups where the Vercel CLI has no route out at all.
For the database side, when the bottleneck is not the link but the number of connections, the answer is usually a pooler rather than an env var. I walk through that pattern in Supabase Connection Pooling with PgBouncer on Vercel Serverless. The two are adjacent problems with different fixes, and it is worth knowing which lever you actually need before tuning anything.
Best practices for local development
My standing rules, in order of how often they prevent incidents:
-
Pin the Vercel CLI version in
devDependencies. The flag's behaviour has changed across majors; pinning means a teammate'spnpm installcannot silently switch it off. -
Put
VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1in.env.localfor projects that never use Vercel-only services locally. It is one line, it is gitignored, and it removes a class of CI failures. -
Keep
vercel dev(the CLI wrapper) andnext dev(the framework command) as separate npm scripts. The flag only matters for the first; the second ignores it. - Treat the flag as a local-dev opt-out, not a deployment setting. It has no effect on the production runtime, and relying on it for production-side behaviour is a category error.
- When the local environment starts to feel fragile — slow boots, hung links, missing modules after a deploy — go back to the basics before reaching for more env vars. I have a post-mortem on exactly that pattern at Next.js Build Passed But Production Broke that I send to teammates when they are stuck.
The general principle is the same as for any experimental flag: keep it scoped, keep it visible, and keep a non-experimental fallback (next dev without the Vercel CLI wrapper) that you can fall back to when the experimental path misbehaves. That is what turns a "I set the flag and it did nothing" panic into a five-minute diagnostic.
FAQ
What does VERCEL_EXPERIMENTAL_DEV_SKIP_LINK do?
It tells the Vercel CLI to skip establishing a local dev link to your Vercel project, which prevents the CLI from pulling or emulating Vercel-only services (KV, Postgres, Edge Config) during local Next.js development.
How do I configure VERCEL_EXPERIMENTAL_DEV_SKIP_LINK for Next.js?
Export the variable in the shell that runs your dev server (export VERCEL_EXPERIMENTAL_DEV_SKIP_LINK=1) or add it to .env.local at the project root, then restart next dev so the new value is picked up.
Can I use VERCEL_EXPERIMENTAL_DEV_SKIP_LINK in production?
No. The flag targets local CLI behaviour during vercel dev and is not part of the Next.js production runtime on Vercel. It has no effect on deployed builds, where Vercel resolves environment variables from the project dashboard.
Why is VERCEL_EXPERIMENTAL_DEV_SKIP_LINK still not working after I set it?
Usually because the variable was not exported into the shell that launches next dev, the .env.local file is in the wrong directory, or the dev server is still running from before the change. Stop the server, re-export, and start it again. If the CLI version is old, upgrade it: the experimental flag contract has changed across majors.
Is VERCEL_EXPERIMENTAL_DEV_SKIP_LINK safe to commit to the repo?
Put it in .env.local, not in .env. .env.local is gitignored by default in Next.js projects, which is the right scope for a local-only opt-out. Committing it to a tracked .env would leak the developer's local configuration into the repository.
Does the flag work with pnpm, yarn, and npm?
Yes. The variable is read by the Vercel CLI process, not by the package manager. As long as the shell that invokes pnpm dev, yarn dev, or npm run dev has the variable exported, or the same value is loaded by next dev from .env.local before the CLI is spawned, the behaviour is the same.
Related
- Next.js Env Variables Not Working on Vercel: 5 Fixes (2026)
- Fix Next.js Module Not Found After Deploy or Production Build
- Next.js Middleware Not Running on Vercel: 3 Causes
- Supabase Connection Pooling with PgBouncer on Vercel Serverless
- Fix ChunkLoadError: Loading Chunk Failed in Next.js
- Next.js Build Passed But Production Broke
Originally published at https://www.iloveblogs.blog
Top comments (0)