You connect a repository, watch the import run, open the preview — and the page loads, but the part you actually wanted is dead. No error you can act on, just a form that does nothing or a list that stays empty. Nine times out of ten this is not a broken agent, a broken import, or a broken app. It is an empty environment, and it is entirely expected.
This guide explains why that happens, what environment keys actually are, and how to get from empty to running without doing something you will regret.
What an environment key is, in one paragraph
Application code should not contain the address of your database or the password to your email provider. Those values differ between your laptop, a staging environment and production, and some of them are secret. So instead of writing them into the code, you write a name into the code and supply the value from outside at runtime. That named value is an environment variable — an environment key with a value attached.
This is why the same codebase can point at a test database on your machine and a real one in production without a single line changing. The code says "read DATABASE_URL"; the environment decides what DATABASE_URL means today.
Why a clone never brings your values
On a developer machine, those values usually live in a file called .env sitting next to the code. And .env is, by near-universal convention, listed in .gitignore — which means Git deliberately refuses to track it.
That convention exists because committing credentials to a repository is one of the oldest and most damaging mistakes in software. Once a secret is in Git history it is in every clone, every fork, every backup, and every laptop that ever checked the project out — and removing it from history is genuinely hard. So the ecosystem settled on: never commit it.
The consequence is simple and unavoidable. Anything that clones your repository — a colleague, a CI runner, a sandbox — receives your code and none of your credentials. Expect the environment keys to start empty. That is the system working correctly, not failing.
A gitignored .env is not an oversight the import forgot to handle. It is the one part of your setup that is deliberately unshareable, and any tool that magically brought it along would be a tool with a security problem.
How to tell this is what is wrong
The symptom has a recognisable signature. Learn it once and you will diagnose it in seconds.
- The page renders. Layout, text and styling are fine — because none of those need a credential.
- One specific capability is dead. A login button that does nothing, a list that never populates, a form that submits into silence.
- There is no useful error on the page. Failures to reach a service usually surface in a server log, not in the interface.
- A banner over the preview counts how many required keys have no value, and links to the tab where you can fill them in.
That last one is the giveaway. If a missing-secrets banner is sitting over your preview saying that a number of required secrets are missing and the preview may not fully work, the diagnosis is done. There is also a warning badge on the Manage tab, so you can spot it without opening the preview at all.
Filling them in
Manage → Environment lists the keys your project declares, grouped by the provider that needs them, each marked set or missing. Grouping by provider is more useful than an alphabetical list, because credentials come in sets — you are rarely missing one Stripe key, you are missing Stripe.
Option one: paste the whole file
The fastest path is to paste an entire .env at once rather than typing keys individually. The text is parsed mechanically by the server: comments and blank lines are ignored, quoted values are unquoted, and no model ever sees the contents. A bulk credential import should be a parsing operation rather than an interpretation one, and it is.
# comments are ignored
DATABASE_URL="postgres://user:pass@host:5432/db"
RESEND_API_KEY=re_xxxxxxxxxxxx
NEXT_PUBLIC_SITE_URL=https://example.comOption two: connect the provider
Manage → Connections is the sibling surface. Attaching a provider account can fill some of those keys for you, and the values then show as coming from that connection rather than as something you typed. Prefer this wherever it is available. It gives you one source of truth: rotate a credential at the provider and you are not hunting for a stale pasted copy three months later.
Secret or variable: the distinction that matters
Every key is one of two things, and getting this wrong in either direction has consequences.
A secret is stored encrypted on the server and injected into your running app. It is never read back to the browser — a key that already has a value shows a mask and an option to clear it, rather than revealing what is stored. If you need to know a secret value, you look it up at the provider that issued it, not here. That is the correct behaviour even though it is occasionally inconvenient.
A variable stays visible and editable. This is what you want for anything that ends up in client-side code, because such a value is not secret in any meaningful sense — it is shipped to every browser that loads the page. Pretending otherwise creates false confidence.
The prefix convention, and why you should respect it
Modern web frameworks use a name prefix to mark a value as intentionally public. On a bulk import, publicly-prefixed keys — the NEXT_PUBLIC_ and VITE_ families — are stored as visible variables, and everything else is stored as a secret. Defaulting unrecognised keys to secret is the right bias: over-protecting a public value costs nothing, under-protecting a private one costs a lot.
The corollary is a rule worth writing on the wall of any team: the prefix does not make a value safe, it makes it published. A database password named with a public prefix is not protected by being called something reassuring. It is in the browser bundle.
A short taxonomy of what usually needs filling
- Database — a connection string containing a host, a user and a password. Always a secret.
- Authentication — a signing secret, plus provider client IDs and client secrets for social sign-in. The ID is often public; the secret never is.
- Email — an API key for the sending provider, plus a verified sending address. The key is a secret.
- Payments — a publishable key that is meant to be public and a secret key that absolutely is not, plus a webhook signing secret. The pairing is a classic place to mix the two up.
- Public configuration — your own site URL, analytics identifiers, feature flags. Genuinely variables.
A useful habit when auditing a list of keys: for each one, ask what happens if a stranger reads it. If the answer is "nothing", it is a variable. If you had to think about it, it is a secret.
Common mistakes
- Pasting production credentials into an experiment. Use test credentials for anything you are still shaping. Payment providers give you a full parallel test environment specifically so you never have to charge a real card to check a flow.
- Copying a value with an invisible newline or a stray quote. This produces an authentication failure that looks like a wrong password. Pasting the whole file avoids it, because the parser handles quoting for you.
- Assuming one missing key. Credentials arrive in sets. If sign-in is broken, check every key that provider needs, not just the one you noticed.
- Leaving a key set to a placeholder. A value of "changeme" is worse than an empty one, because it reads as set and fails at runtime instead of being flagged as missing.
- Fixing it in the running app instead of at the source. If the value came from a connection, change it at the provider, not by pasting an override.
What good hygiene looks like afterwards
Once the app runs, a few habits keep it that way and keep you out of trouble.
- Keep .env gitignored, permanently. The moment it becomes convenient to commit it is the moment to resist hardest.
- Keep a committed .env.example with the key names and no values. It documents what the app needs without revealing anything, and it makes the next import trivial.
- Rotate anything that has ever been pasted somewhere you are unsure about. Rotation is cheap; investigating an exposure is not.
- Keep test and production credentials clearly distinguishable by name so nobody has to guess which environment they are pointed at.
- Prefer a connection to a pasted key whenever the provider supports it, so there is one place to change a credential rather than several.
Why this is worth understanding rather than just working around
It would be possible to treat all of this as a chore and click through it. But environment configuration is one of the few genuinely portable pieces of knowledge in web development: it works the same way on a laptop, in a CI pipeline, in a container, and in a managed sandbox, because it is a convention rather than a product feature.
Learn it once and every future "why does this work locally but not deployed" question gets shorter. That question, at least half the time, has the same answer — the environment is different, because the environment is supposed to be different.