Skip to main content
ASoc
Comparison

Vercel vs. GitHub Pages: 344 Static Pages, 8 That Need a Server

GitHub Pages has no compute layer at all — not slower, absent. A fresh build of this storefront counts exactly which routes that categorically rules out, and why it isn't a rendering-mode question.

The ASoc Team8 min read

GitHub Pages serves static files over a CDN — no server, no functions, no runtime of any kind. Vercel runs Next.js with serverless functions, middleware, and dynamic rendering alongside the static output. The question isn't which is faster for a static page; it's which parts of a real app need compute at all. This storefront's own build draws that line precisely: 344 statically generated pages that either platform could serve identically, and 8 route patterns that require a server to exist, full stop — not a slower server, no server.

The decision in one table

GitHub PagesVercel
What it servesStatic files, over GitHub's CDNStatic files, plus serverless functions and edge middleware
Server-side codeNone — zeroRoute Handlers, Server Actions, middleware, all first-class
Custom domain + HTTPSYes, automaticYes, automatic
Framework awarenessNone — you bring a build outputDeploys Next.js's own output format directly, ISR and ƒ routes included
Cost at this project's scaleFree, no tiersFree tier covers this project; usage-based beyond it
What it cannot doRun any code on a requestNothing this project needs — everything below runs here today

What "no server" actually excludes, measured from this build

A fresh production build of this site (pulled while writing this post) reports 344 statically generated pages against 8 dynamic route patterns: /api/download, /api/webhooks/lemonsqueezy, /auth/callback, /dashboard, /dashboard/settings, /login, /reset-password, /signup — plus the proxy middleware running in front of every one of them. The Next.js-vs-Angular post already cites this build's static count for a different comparison; this post is about the other eight.

Every one of those eight needs code to execute on the server, on a request, and none of them can be pre-baked into a static file no matter how the rendering mode is configured:

// src/app/api/webhooks/lemonsqueezy/route.ts
export const runtime = "nodejs"; // required for node:crypto

export async function POST(req: Request) {
  const raw = await req.text();               // exact bytes, for the HMAC
  const signature = req.headers.get("X-Signature");
  // ...verify the signature, then grant entitlements in Postgres
}

That's LemonSqueezy's payment webhook — 69 lines that read a raw request body, verify an HMAC signature with node:crypto, and write to the database on every successful order. There is no static output of "having verified a signature this second" to generate ahead of time; the whole point is that it runs when a payment happens, which is a moment GitHub Pages has no way to observe, let alone act on. The gated-download route (157 lines) and the OAuth callback (26 lines, exchanging a PKCE code for a session and setting the cookie) are the same shape: each one has to run now, against this specific request's data, on a server that exists.

Even src/proxy.ts — the Next.js middleware refreshing the Supabase session on nearly every request — has nothing to do with dynamic rendering as a Next.js concept. It's compute that runs before a response is built, on GitHub Pages' architecture there is no "before the response" step to run it in: a static host serves the file that matches the URL, or a 404, and that is the entire request lifecycle.

The distinction this comparison isn't about

It would be easy to read this as a rendering-mode argument — "use force-static, ship everything as HTML, then any host works" — but that's a different post's territory. The static-rendering audit already covers the four things that push a Next.js route off static rendering (the OG-image route's own generateStaticParams requirement, searchParams on /login, cookies() in a shared layout, and the deliberate CSP-nonce trade-off this storefront didn't take) — none of that is this post's subject. GitHub Pages' limitation is categorical, not a rendering-mode choice: it has no compute layer to opt into even if every one of those four triggers were somehow eliminated. A webhook receiver cannot become a static file under any configuration, because its entire job is to react to an event that hasn't happened yet at build time. Vercel's serverless functions exist for precisely the code that can't be that build-time artifact; GitHub Pages was never trying to solve that problem, which is a legitimate design choice for what it's built for — plain content — and a hard wall for anything that isn't.

Where GitHub Pages is straightforwardly the better answer

  • Documentation sites, changelogs, and portfolios with no backend at all. If every page in the build output is (fully static) with no ƒ routes anywhere, GitHub Pages serves it for free, forever, with no usage-based billing to watch.
  • A repo that already lives on GitHub and needs zero additional infrastructure. gh-pages branch or /docs folder, one settings toggle, done — no separate platform account.
  • Projects that will never need a webhook receiver, an auth callback, or a rate-limited API route. The moment any of those becomes real, so does the need for compute GitHub Pages doesn't offer.

None of those describe a storefront that sells licensed downloads, verifies payment webhooks, and gates files behind a rate limiter — the eight dynamic routes above aren't incidental; they're the commerce layer the whole site exists to run.

Mistakes and how they show up

MistakeSymptomFix
Assuming output: "export" makes any Next.js app GitHub-Pages-readyRoute Handlers, middleware, and Server Actions silently don't work in a static exportAudit for ƒ routes in the build output first; their existence rules out static export entirely, not just this host
Treating "static site" and "no backend" as interchangeableA payment webhook or auth callback gets bolted onto a static host via a third-party function service, adding a second platform to operateIf the app already needs server code, deploy it somewhere that runs server code natively
Choosing a host by framework popularity rather than by what the app's routes actually needA webhook-dependent app on a platform with no compute, discovered only when the first real payment fails to recordCount the ƒ routes in your own build output before picking a host, the way this post does
Assuming Vercel is required for any Next.js appPays for serverless infrastructure a genuinely static Next.js build (zero ƒ routes) doesn't needA fully static Next.js export can run on GitHub Pages, Cloudflare Pages, or any static host — check the build table first
Running a webhook receiver on a free static host via an external function add-onAdds a second vendor, a second auth boundary, and a second thing to monitor for the one part of the app that actually needs a serverPut the whole app on one platform that natively runs both the static and dynamic halves

Frequently asked questions

Could this storefront run on GitHub Pages if the commerce layer moved to a third-party service? The static 344 pages could, today, unchanged. The 8 dynamic routes would need to move to something that runs server code — a separate serverless platform, a small VPS — which trades one platform for two, and re-introduces exactly the operational surface GitHub Pages was chosen to avoid in the first place.

Does GitHub Pages support any server-side logic at all, like GitHub Actions? GitHub Actions can run code on a schedule or on a push, and could, for instance, regenerate static files ahead of time. It cannot run code in response to an incoming HTTP request the way a webhook receiver needs — a payment event doesn't wait for the next scheduled workflow run.

Is Vercel's free tier enough for a project this size? This storefront's actual usage is dominated by static page serves, which Vercel's CDN handles the same way any static host would; the eight dynamic routes are the delta a static host can't offer at any price, so the comparison isn't really about the free tier's limits.

What's the simplest way to tell if my own Next.js app needs Vercel-style hosting or could use GitHub Pages? Run next build and read the route table it prints. Every ƒ is a route that needs a server on every request; every / is a file GitHub Pages could serve unchanged. Zero ƒ routes means a static host is a real option; any ƒ routes at all mean it isn't, regardless of how few.

Templates in this post

ASoc Beacon markets mobile-device-management software with a device-console dashboard mock and a six-capability fleet grid — a marketing site with no backend of its own, and a reasonable GitHub Pages candidate if it ever shipped standalone. ASoc Beaker is a science-laboratory services site with a capability matrix and two pricing tiers. ASoc Blueprint is an app-development agency site with a three-step delivery process and project-based pricing.

Browse the full sets: Next.js landing page templates, Tailwind landing page templates. For the Next.js-specific triggers that push a route off static rendering, the static-rendering audit; for this build's own route census used from a different angle, Next.js vs. Angular.

Keep reading

Comparison10 min read

Vercel vs. Render: Does Anything You Run Need to Stay Running?

Two Route Handlers, both stateless, neither overriding the default timeout — versus a release script that clones, installs, and boots for minutes. That split is the real decision axis.

Read more
Comparison8 min read

Vite vs. Vue: A Build Tool and a Framework Are Not Alternatives

Vite builds, Vue renders, and most projects use both. The comparison that resolves is Vite vs. Vue CLI — plus which layer to blame when a build breaks.

Read more
Comparison9 min read

Vite vs. Webpack: 488 Pages Built by a Repo Running Neither

Most comparisons assume you pick a bundler. On a framework you inherit one — and the two config lines this codebase writes anyway are where the real difference shows.

Read more