
Photo by Taylor Vick on Unsplash
Most personal sites and small product blogs do not go down because Vercel is unreliable. They go down because someone added a clever edge rewrite, an expired preview token, a production env var that only existed on a laptop, or a build that "worked on my machine" with a different Node version.
This is a setup for a Next.js App Router site — Wordstack is one — that is allowed to be boring. The goal is not a perfect score on a conference talk. The goal is that git push to main becomes a site that stays up, and that a broken pull request cannot take production with it.
What "boring" includes
Assume a public or private GitHub repo and a Next.js app that already builds locally with npm run build. You do not need a container, a second region, or a queue.
The pieces you actually need:
- A Vercel project linked to the repo.
- A production branch (
main) and preview deployments for everything else. - Environment variables that exist in Production and Preview if the preview is supposed to look real.
- A domain you control, pointed exactly the way the dashboard says.
- A build command you did not customize for no reason.
That is enough for a blog, a docs site, and a surprising number of product marketing sites. If you are running a multi-tenant SaaS with background jobs, you are not in this post. You can still start here and graduate when a real constraint appears.
Create the project like a grown-up
Import the repo. Keep the framework preset on Next.js. Leave the build command as next build and the output as the Next.js default. If you feel the urge to write a custom install script, stop and fix package-lock.json instead.
Set the Node version in the project to match what you use locally. Do not let production float on "latest" while your laptop is on the version from last winter. The Vercel project setting and a local .nvmrc or Volta pin are cheaper than a one-line syntax surprise.
If the repo is a monorepo, set the root directory once and do not invent a turbo pipeline for a single site. Wordstack is not a monorepo. Yours does not need to become one to look serious.
Environment variables without folklore
Put secrets in the Vercel dashboard. Do not put them in GitHub Actions "just for now." Do not commit .env because the file is in .gitignore most of the time.
Split variables by intent:
NEXT_PUBLIC_*values are public. Treat them as public even in preview.- Server secrets (
AMAZON_ASSOC_TAGon this site is one) stay off the client. - Preview should get safe versions of keys. Production keys in preview is how a test post hits a live API.
For Wordstack specifically, three variables matter and none of them should be invented:
NEXT_PUBLIC_SITE_URL— the canonical origin. Set this to the real domain in production so Open Graph tags, the sitemap, and RSS do not advertise a deployment URL.AMAZON_ASSOC_TAG— only when you have a real Associates tag.NEXT_PUBLIC_ADSENSE_CLIENT— only when you have a real AdSense client. The ad components on this site render nothing without it.
A preview deployment can use NEXT_PUBLIC_SITE_URL pointed at that preview host if you are debugging canonical tags. For a blog, it is also fine to leave the production URL in place and accept that preview metadata looks like production. Pick one and write it down. Do not let each PR guess.
Pull env locally with vercel env pull when you need it. Do not maintain a second Slack message that is "the real .env."
Domains and DNS
Buy the domain wherever you already think. Point it at Vercel with the exact records the dashboard shows. If you add a www and an apex, pick one as canonical in the Next.js metadataBase and redirects. Two live hosts with the same site and no canonical is how you split your own SEO.
TLS is automatic. Do not install a certificate by hand unless Vercel told you to. If a domain is "pending," the problem is almost always a leftover A record or a proxy toggle at the registrar, not Next.js.
Email on the same domain is a separate product. Do not break MX records because you pasted an A record over the whole zone. This is the most common way a "simple blog setup" also takes down the owner's inbox.
Preview deployments are the whole quality process
Every pull request should get a URL. That URL is the review. If a change cannot be checked there — an RSS route, a 404, dark mode, an MDX post — it is not ready.
A few rules that keep previews useful:
- Do not protect yourself into a corner. If the site is a public blog, previews can be public. If you ever add a draft post you do not want indexed, keep drafts out of
mainand out of public preview, or use a mechanism you actually understand. - Click the preview on a phone once. Most "the layout is fine" bugs are 390 pixels wide.
- If
npm run buildfails on Vercel and works locally, diff Node, the lockfile, and env vars before you "fix" the app.
Vercel comments on the PR. Use them. Do not also run a second host "for staging" until you have a reason the preview URL cannot serve.
Features you should ignore until they hurt
Vercel will offer you a lot of surface. Most of it is optional for a publication:
- Edge middleware. A blog does not need to inspect cookies on every request. If you add middleware, it should have a one-sentence job (redirect
/blogto/posts, for example) and a test. - Cron. Fine for a scheduled rebuild or a cleanup job. Not a reason to invent a platform.
- KV, Blob, Postgres. Add them when a feature needs them. A folder of MDX files is a database with a git history.
- Analytics and Speed Insights. Useful when you have traffic. They are not a launch requirement.
- ISR science. For a blog that updates when you commit, static generation is enough. Do not tune revalidate numbers you cannot explain.
The same restraint applies to Next.js itself. You do not need a route handler for every page. You do not need a CMS on day one. You need npm run build in CI and a production URL that matches the domain on the certificate.
When it breaks
Builds fail for three boring reasons: TypeScript, a missing env var, and an MDX file with broken frontmatter. Read the log from the top of the error, not the framework poetry at the bottom.
Runtime 404s on a new post usually mean the slug does not match the filename or you are looking at an old deployment. Runtime 500s on Open Graph images usually mean the post lookup threw. Fail those images loudly in development so you do not debug them in the WhatsApp link preview.
If production is wrong and preview was right, you shipped a different env or a different branch than you thought. The dashboard deployment list is the source of truth, not the last terminal you looked at.
Cost and the "just stay online" part
Hobby is enough for a blog until it is not. The things that surprise people are not page views. They are image optimization on a firehose, a serverless function that runs on every request because you marked a static page dynamic, and preview deployments you never delete because every branch lives forever.
Keep pages static unless they must read a request. Wordstack's pages are generated from files. That is the point. If you add cookies() or headers() for no reason, you have donated your site to the function meter.
When a bill appears, look at the usage tab before you rewrite the stack. The rewrite will not be cheaper if the cause was "we generated a PNG on every hit."
A setup you can leave alone
The boring Vercel setup is: GitHub pushes to Vercel, main is production, PRs are previews, env lives in the dashboard, the domain is pointed once, and the site is static enough that a traffic spike is a CDN problem, not yours.
You can add complexity later. You will not get back the weekend you spent making a blog "multi-region." Ship the post. Leave the platform boring.