Skip to content
Chapter 98Lesson 2

From repo to live URL

Import a GitHub repo into Vercel and ship your first production deployment, the entry point to deploying a 2026 SaaS app on Vercel.

Last lesson you learned what a git push does once a repo is wired to Vercel: which git event maps to which deployment, why production is just an alias pointing at one immutable build, and what the deploy pipeline runs through on its way to a URL. This lesson is where you make that first push actually happen.

By the end you’ll have two things. The first is a public *.vercel.app URL serving your repo as a real production deployment: your code, on the internet, reachable by anyone you send the link to. The second is the small CLI routine every teammate runs once per clone to connect their local machine to the project, made up of vercel link and vercel env pull.

The whole import takes maybe five minutes of clicking, and on most of those clicks Vercel has already detected the right answer and you’re just confirming it. So the interesting question isn’t “how do I deploy,” because the platform makes that nearly automatic. The interesting question is the one an experienced engineer asks while clicking through: which of the defaults Vercel fills in will quietly harden into something expensive to change later, and which can I accept without a second thought? You accept most of them and move on, and you slow down for two. That distinction is what this lesson is built around.

One note to set your expectations before you start: after this first deploy, the app will boot but it won’t be fully functional. Anything that reads a secret, such as the database, Stripe, or email, stays dark, because you’re going to skip environment variables on this first deploy and wire them in a later lesson. That’s deliberate staging, not a defect. The goal right now is a structurally live deployment, not a finished product.

Before you import: the three things the repo needs

Section titled “Before you import: the three things the repo needs”

The import flow assumes a few things are already true about your repo and your account. Two of them you’ve already handled. The third causes a hard-to-trace problem if it’s missing, so it’s worth a look before you click anything.

A GitHub repo with a green main. The CI gate you built in the previous chapter is live: every push to main has to pass the type-check, lint, and test jobs before it lands. That’s the whole reason you built the gate first, so that what you’re about to deploy is already proven, not hopeful.

A Vercel account. It’s free to create. This course assumes the Pro tier from launch onward.

The packageManager field in package.json. This is the field that bites you if it’s missing. Vercel decides which package manager to use by reading it. With it present, Vercel runs your install with pnpm, using your committed lockfile, exactly the way your machine does. Without it, the build silently falls back to npm. Now the install behavior on the server has drifted from the install behavior on your laptop, which is precisely the kind of “works on my machine” gap you don’t want sitting under a production build.

The field pins an exact version, not a range, so the builder resolves the same pnpm your team runs.

package.json
{
"name": "acme-app",
"private": true,
"packageManager": "pnpm@11.5.0"
}

This is the core procedure: connecting your GitHub repo to a new Vercel project. The clicks are simple. There’s exactly one decision worth slowing down for, so let’s name it before the steps.

When you connect Vercel to GitHub, you grant Vercel’s GitHub App access to some set of your repositories. The cost of getting that choice wrong falls unevenly on the two directions. If you scope it too narrowly and need to add a repo later, that’s a quick change in settings. If you scope it too broadly, granting access to your whole account or org “to be safe,” pulling that access back later usually means uninstalling and re-installing the App, which is a more disruptive cleanup. Because the two directions aren’t equally costly, take the reversible one: scope access to the single repo you’re deploying, and widen it later only if you actually need to.

  1. In the Vercel dashboard, click Add New → Project.

  2. Connect the Vercel GitHub App through the OAuth prompt. When GitHub asks which repositories Vercel may access, choose Only select repositories and pick the one repo for this project, not All repositories. This is the unevenly-costly choice from above, so take the narrow side.

  3. Back in Vercel, find your repo in the list and click Import.

This is also the moment to recall the trust model from last lesson, because you’re literally clicking “Install” on it. Vercel connects to GitHub through OAuth and reacts to your commits; it doesn’t push your application code. The App does hold some write scopes, which it needs to post deployment statuses and read your workflow logs. So it isn’t that Vercel can never write to your repo; it’s that it never authors your application code. The commits stay yours.

The GitHub App install screen. Choosing 'Only select repositories' is the one click in this flow with asymmetric cost.

The Configure Project screen: accept four defaults, slow down on two

Section titled “The Configure Project screen: accept four defaults, slow down on two”

After you import, Vercel inspects the repo and shows the Configure Project screen with six fields already filled in. This is where the lesson’s central question gets concrete. Vercel auto-detected all six, and for a single-app Next.js 16 repo like yours, four of them are correct and one click away from done. The other two carry a cost if they’re wrong, and that cost shows up later, after the project has settled and people have started relying on it.

Sort them into two groups.

Leave these. They’re auto-detected and correct for this repo, so touching them only invites mistakes.

  • Framework Preset, detected as Next.js. This tells Vercel how to build and serve the app. It’s right, so leave it.
  • Build Command, next build. Later, when you wire per-PR database branches, you’ll override this to run a migration step before the build. That’s a future lesson’s job, not something you do now.
  • Output Directory, .next, where next build writes its output. Leave it.
  • Install Command, pnpm install. This is the packageManager field paying off: because you pinned pnpm, Vercel installs with pnpm instead of npm. The loop you opened two sections ago closes right here.

Look twice at these. Both are cheap to set correctly now and annoying to change once the project has a history.

  • Project name. This becomes your *.vercel.app subdomain. Rename it later and you abandon the old URL, so every link anyone saved breaks. Pick a stable, real name now, the one you’d be comfortable seeing in a URL for the life of the project.
  • Root Directory. For the single-app repo this course ships, it’s ./, because the app lives at the repo root, and that’s already what Vercel guessed. You’d only point this at a subdirectory in a monorepo, where one repo holds several apps. That’s not your situation, so confirm ./ and move on.

Then there’s the field you deliberately leave empty: Environment Variables. Skip them on this first deploy. This is safe and intended, but it’s worth being clear about why, so you don’t misread the result. The app will build and boot fine, because the structure is all there. But anything that needs a secret to do its job, like connecting to the database or talking to Stripe, won’t work yet, because the values it reads aren’t set. You’ll scope those variables properly in a later lesson. For now, expect a deployment that’s structurally live but unconfigured. That’s the correct outcome of this step, not a failure.

With env vars left empty and the two look-twice fields confirmed, click Deploy.

Vercel's Configure Project screen. Four fields are auto-detected and correct; Project name and Root Directory are the two that cost you later if they're wrong; Environment Variables is left empty on purpose.

Before moving on, sort the fields yourself.

Sort each Configure Project field by whether you accept Vercel's auto-detected default or stop to check it before deploying. Drag each item into the bucket it belongs to, then press Check.

Accept the default Auto-detected and correct for a single-app repo
Look twice Cheap now, costly to change later
Framework Preset
Build Command
Output Directory
Install Command
Project name
Root Directory

Click Deploy and the build starts streaming live in the dashboard. An experienced engineer reads this first log top to bottom exactly once, not to hunt for bugs, since CI already cleared the code, but to take a snapshot of the app’s shape on the platform. The order it runs in shouldn’t surprise you: install, then build, then package the output. You ordered that pipeline last lesson, and this is it running.

Three things are worth a glance, in the order they scroll past.

The pnpm install output. Skim it for warnings, such as a peer-dependency mismatch or a deprecation notice, and confirm it says pnpm, not npm. That’s the packageManager field proving itself one last time. If you ever see npm here, the field is missing or wrong.

The next build route summary. When the build finishes compiling, it prints a table of every route, marked static or dynamic. A static route is pre-rendered once at build time and served as a ready-made response; a dynamic route runs per request, on demand. You don’t need the full theory of that split here. What matters is why an experienced engineer glances at this table: it’s a free snapshot of the app’s performance profile. At a glance you see which routes are cheap pre-rendered pages and which do work on every request. If something you expected to be static shows up dynamic, this is the earliest place you’d notice.

The function bundle sizes. Vercel reports the size of each server function and warns if one is oversized. A surprisingly large function usually means a heavy dependency leaked into a server bundle that didn’t need it. Just note any warning for now; chasing down what bloated a bundle is a later lesson’s concern.

Terminal window
Route (app) Size First Load JS
/ 1.2 kB 95 kB
/pricing 0.8 kB 92 kB
ƒ /dashboard 2.1 kB 110 kB
ƒ /invoices/[id] 1.7 kB 104 kB
ƒ /api/webhooks/stripe 0 kB 0 kB
(Static) prerendered as static content
ƒ (Dynamic) server-rendered on demand

The build finishes, and your-project-name.vercel.app is now your production deployment, live on the public internet.

Take a moment to register what just happened: your code went from a private repo to a running app anyone can reach. That’s a real line crossed, and it’s the whole reason the chapter exists.

Two caveats keep your expectations calibrated.

The app isn’t fully functional yet. With no environment variables set, every secret-backed feature is dark. That follows from skipping env vars on this deploy, and you’ll light them up in a later lesson.

This *.vercel.app name is a fine live URL for now, but treat it as a development artifact, not the app’s permanent address. A custom domain replaces it as the canonical URL in a later lesson.

That manual import was a one-time bootstrap. From now on, the model from last lesson runs on its own, with zero clicks in the dashboard for normal work. It has two halves.

Push to main, get a production rebuild. The next merge to main triggers a fresh production deployment automatically and re-points the alias at it. That’s the git-event-to-deployment mapping, now real.

Open a PR, get your first preview URL. Push a branch and open a pull request, and the Vercel GitHub bot comments on the PR with a “Visit Preview” link of the shape your-app-git-<branch>-<org>.vercel.app. It’s a full, working build of your branch, billed against the project. That’s the preview half of the git-event map, paying off.

One warning here you have to carry with you, because you’re about to be standing in it.

One more thing shows up on every PR. Two sets of checks line up at the bottom: Vercel’s own build check and the CI checks from the previous chapter. Both have to be green before the branch can merge, the deploy and the gate side by side.

When you do start sharing previews with people outside the team, turn on preview password protection first. That’s a Pro feature, and a later lesson covers the toggle. It’s named here so you know it exists before you need it.

A pull request after a push: the Vercel bot's preview link at the top, and the row of status checks — Vercel's build plus the CI gate — that must all be green to merge.

The Deployments list: your first stop when something looks wrong

Section titled “The Deployments list: your first stop when something looks wrong”

The dashboard’s Deployments tab lists every deployment your project has ever produced, newest first. Each row shows the commit SHA, the branch, the build status, the environment, and how long ago it ran.

Its job is to be the first place you look when something’s off in production. One row carries a green Production badge, marking the deployment the production alias currently points at, the build serving live traffic. Tie that back to last lesson: the alias points at exactly one deployment, and this list is where you actually see which one. It’s also the surface a later lesson’s rollback flow operates on, since recovering from a bad deploy is mechanically just re-pointing the alias at an earlier row in this list. For now, just know where the list is and what the badge means.

The Deployments list. The row carrying the Production badge is the build currently serving live traffic.

When you’d add a vercel.json (usually you don’t)

Section titled “When you’d add a vercel.json (usually you don’t)”

A question naturally surfaces about now: do I need a config file for any of this?

The answer, up front, is no. A default Next.js project on Vercel’s standard runtime needs no vercel.json at all. Vercel infers everything you saw on the Configure Project screen. You reach for a config file only for a few specific needs, and all of them belong to later lessons:

  • headers, to set security headers on responses, which a later lesson covers.
  • A per-route runtime override, when one route needs different execution settings than the rest, also later.
  • crons , with a caveat: this course runs scheduled work through Trigger.dev, not Vercel crons, so in practice you won’t use this one at all.

The rule of thumb to carry: most SaaS projects ship with either no vercel.json or a tiny one, a dozen lines at most. Reaching for it early, before you have a concrete need, usually signals that you’re configuring around a problem instead of solving it.

If you ever do add one, it’s small. A minimal file setting a single response header looks like this. Don’t add it now, though; you’ll write the real version when the security-headers lesson calls for it.

vercel.json
{
"headers": [
{ "source": "/(.*)", "headers": [{ "key": "X-Frame-Options", "value": "DENY" }] }
]
}

The import wired the repo to Vercel. This last step wires a local clone to the Vercel project, and unlike the import, which happened once, every teammate runs it once on every fresh clone, forever. It’s the modern replacement for the old habit of copying a .env.example and filling in secrets by hand.

Run two commands from the repo root.

  1. vercel link associates this local directory with the Vercel project. It writes a small .vercel/ folder, which is gitignored: that linkage is specific to your machine, not shared configuration, so it doesn’t belong in the repo.

  2. vercel env pull .env.local pulls the project’s Development-scoped environment variables down into a local .env.local file. vercel env pull defaults to the Development environment, which is exactly the set of values local development wants. Rerun it whenever someone on the team adds or changes a development variable.

Terminal window
vercel link
vercel env pull .env.local

Two points are worth holding onto from this.

.env.local is gitignored, and that’s the point, not an oversight. The real values live on Vercel under the Development scope, which is the source of truth. The file is just a local copy of them, which is why you pull it instead of committing it. How those values get scoped to development versus production is a later lesson’s full story; for now, just know the values live on Vercel.

Keep a .env.example anyway. It isn’t redundant with vercel env pull, because the two do different jobs. vercel env pull fetches the real values, but only for someone who has access to the Vercel project. .env.example is documentation: a committed file listing every key the app needs, with placeholder values, so a contributor without Vercel access can still see the shape of what the app expects. One supplies the values, the other supplies the map. A well-run repo keeps both.

A later lesson tells the full three-environment scoping story: development, preview, and production, and which secrets each one sees. For now you’ve got what you need: the values live on Vercel, and vercel env pull brings the development set to your machine.

Finally, put the whole procedure in order.

Order the steps that take a green-main repo to a working local and deployed setup. Drag the items into the correct order, then press Check.

Add the packageManager field to package.json
Add New → Project, and install the GitHub App scoped to the one repo
Accept the auto-detected build settings, skip environment variables, and Deploy
Watch the first build log and note the route summary
Reach the first *.vercel.app production URL
Run vercel link in the clone
Run vercel env pull .env.local