Skip to content
Chapter 98Lesson 2

From repo to live URL

Import a GitHub repo into Vercel and ship your first production deployment.

Last lesson covered what a git push does once a repo is wired to Vercel. This lesson makes that first push happen.

By the end you’ll have two things: a public *.vercel.app URL serving your repo as a real production deployment, and the per-clone CLI routine (vercel link and vercel env pull) every teammate runs once to connect their machine to the project.

The import is five minutes of clicking, and Vercel auto-detects most answers for you. You accept almost all of them and slow down for two.

After this deploy the app boots but isn’t fully functional. Anything that reads a secret (the database, Stripe, email) stays dark, because you skip environment variables this time and wire them in later. That’s deliberate: the goal now is a structurally live deployment, not a finished product.

Before you import: the three prerequisites

Section titled “Before you import: the three prerequisites”

The import flow assumes three things are already true. You’ve handled the first two; the third fails silently if you skip it, so check it before you click anything.

A GitHub repo with a green main. The CI gate from the previous chapter is live: every push to main must pass type-check, lint, and test before it lands. What you deploy is already proven.

A Vercel account. Free to create. This course assumes the Pro tier from launch onward.

The packageManager field in package.json. Vercel reads this field to choose a package manager. With it present, Vercel installs with pnpm against your committed lockfile, exactly as your machine does. Without it, the build silently falls back to npm, so the server’s install drifts from your laptop’s, the classic “works on my machine” gap under a production build. Pin an exact version, not a range.

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

Connecting your repo to a new Vercel project is mostly simple clicks, with one decision worth slowing down for.

When you connect Vercel to GitHub, you grant its GitHub App access to some set of your repositories, and the cost of that choice is lopsided. Scoping too narrowly is a quick settings change to fix; scoping too broadly means uninstalling and reinstalling the App to pull access back. Take the reversible side: grant access to the single repo you’re deploying, and widen it later only if you 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.

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

Clicking Install accepts the trust model from the last lesson: Vercel reacts to your commits. The App does hold write scopes, which it needs to post deployment statuses and read your workflow logs, but 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.

After you import, Vercel inspects the repo and shows the Configure Project screen with six fields already filled in. For a single-app Next.js 16 repo, four are correct as-is. Two others carry a cost that only shows up later, once people rely on the project.

Leave these. Auto-detected and correct for this repo, so touching them only invites mistakes.

  • Framework Preset, Next.js. Tells Vercel how to build and serve the app.
  • Build Command, next build. A later lesson overrides this to run a migration step before the build; not now.
  • Output Directory, .next, where next build writes its output.
  • Install Command, pnpm install. The packageManager field paying off: because you pinned pnpm, Vercel installs with pnpm, not npm.

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

  • Project name. This becomes your *.vercel.app subdomain. Rename it later and every saved link to the old URL breaks, so pick a stable, real name now.
  • Root Directory. For this single-app repo it’s ./, since the app lives at the repo root, which is what Vercel guessed. You’d only change it for a monorepo, where one repo holds several apps. Confirm ./ and move on.

Leave Environment Variables empty on purpose. The app still builds and boots, but anything that needs a secret, like the database or Stripe, stays dark until a later lesson sets the values. Expect a deployment that’s structurally live but unconfigured.

With env vars 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 streams live in the dashboard. Read it top to bottom once, not to hunt for bugs (CI already cleared the code) but to snapshot the app’s shape on the platform. It runs in the order you assembled last lesson: install, build, package.

Three things are worth a glance.

The pnpm install output. Skim it for warnings, like a peer-dependency mismatch or a deprecation notice, and confirm it says pnpm, not npm. If you see npm here, the packageManager field is missing or wrong.

The next build route summary. Once compilation finishes, 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. The table is a free snapshot of the app’s performance profile, and the earliest place you’d notice a route running dynamic when you expected it static.

The function bundle sizes. Vercel reports the size of each server function and warns if one is oversized, which usually means a heavy dependency leaked into a server bundle that didn’t need it. Note the warning for now; tracking down the cause is a later lesson.

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. Your code just went from a private repo to an app anyone can reach.

Two caveats. With no environment variables set, every secret-backed feature is dark until a later lesson. And this *.vercel.app name is a fine live URL for now, but treat it as a development artifact: 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 workflow runs on its own, with zero dashboard clicks for normal work.

Push to main, get a production rebuild. The next merge to main automatically triggers a fresh production deployment and re-points the alias at it.

Open a PR, get a preview URL. Push a branch and open a pull request, and the Vercel GitHub bot comments a “Visit Preview” link of the shape your-app-git-<branch>-<org>.vercel.app: a full, working build of your branch, billed against the project.

Every PR also shows two sets of checks at the bottom: Vercel’s own build check and the CI checks from the previous chapter. Both must be green before the branch can merge.

Before sharing previews outside the team, turn on preview password protection, a Pro feature a later lesson covers. 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 dashboard’s Deployments tab lists every deployment your project has ever produced, newest first, each row showing the commit SHA, branch, build status, environment, and age. This is the first place you look when something’s off in production.

One row carries a green Production badge, marking the deployment the alias currently serves. A later lesson’s rollback flow operates on this list, since recovering from a bad deploy is just re-pointing the alias at an earlier row. For now, 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.

Most of the time, you don’t. A default Next.js project on Vercel’s standard runtime needs no vercel.json; Vercel infers everything you saw on the Configure Project screen. You reach for a config file only for a few specific needs, all covered in later lessons:

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

Most web apps ship with no vercel.json or a tiny one, a dozen lines at most. Reaching for it before you have a concrete need usually means you’re configuring around a problem instead of solving it.

When you do add one, it stays small. A minimal file setting a single response header looks like this; 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 step wires a local clone to the project, and unlike the one-time import, every teammate runs it on every fresh clone. It replaces 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 gitignored .vercel/ folder: 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 into a local .env.local file. It defaults to the Development environment, the right set for local development. Rerun it whenever someone on the team changes a development variable.

Terminal window
vercel link
vercel env pull .env.local

.env.local is gitignored on purpose. The real values live on Vercel under the Development scope, the source of truth; the file is just a local copy, which is why you pull it instead of committing it. How those values get scoped to development versus production is a later lesson.

Keep a .env.example anyway, because it does a different job. vercel env pull fetches the real values, but only for someone with access to the Vercel project. .env.example is committed documentation: every key the app needs, with placeholder values, so a contributor without Vercel access can still see the shape the app expects. One supplies the values, the other the map. Keep both.

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