"Static" means files served as-is: HTML/CSS/JS, including a compiled React/Vue/Vite build. You can put it online for free and with HTTPS on several platforms. Commands verified against the official GitHub Pages, Netlify CLI and Cloudflare Wrangler docs (2026-06).
🧭 The big picture
What you end up with: a public link to your site with a padlock (HTTPS) that stays up permanently and costs nothing. Three equally good routes — pick the one that fits your habits and needs.
First, a fork in the road: static or backend? These platforms serve ONLY static files. If you have server-side logic, a database, secret keys on the server — you need a real server (see the "Your own project on a server from scratch" guide). A landing page, a portfolio, documentation, a compiled React/Vue app with no backend of its own — perfect for static hosting.
What counts as deploy-ready: a folder with an index.html (and assets). For React/Vite that means the build output (npm run build → a dist/ or build/ folder), not the sources.
Three routes: GitHub Pages (if the code is already on GitHub) · Netlify (the most convenient for SPAs, drag-and-drop or CLI) · Cloudflare Pages (fast global network, deploy with one command). Pick ONE.
💡 Before deploying a compiled frontend, mind the base path: on GitHub Pages the site often sits under a sub-path
/repo-name/, and assets referenced from/break (white screen). Netlify/Cloudflare usually serve from the root — simpler. Details in the common mistakes below.
Step 1. Prepare the folder (build it if needed)
What we're doing: getting a static folder ready to publish.
- Plain HTML/CSS/JS — the folder is already good to go (it has
index.htmlin it). - React/Vue/Vite and friends — make a production build:
npm install # once, if you haven't installed dependencies yet
npm run build # creates dist/ (Vite) or build/ (CRA) — that folder is your static site
How to tell it worked: a dist//build/ folder appeared with index.html and assets. You can check locally: npx serve dist → open the address it prints, the site works.
Common mistakes:
- Trying to publish the React SOURCES (with
src/,node_modules/) instead of the build → white screen/errors in the browser. Deploy only thebuildoutput. npm run buildfails with errors → fix the build locally first; a broken build won't magically go live working.
Step 2 (Route A). GitHub Pages — if the code is already on GitHub
What we're doing: turning on free hosting right inside the repository.
- Push the project to a public GitHub repository (see "Git in 15 minutes"). For a compiled frontend — put the build output in the root or in a
/docsfolder (or use agh-pagesbranch). - Repository → Settings → Pages → Source: Deploy from a branch → branch
main, folder/(root)or/docs→ Save. - About a minute later the site is live at
https://YOUR_USERNAME.github.io/repository-name/.
How to tell it worked: on Settings → Pages a banner appears — "Your site is live at …" — with a link; the link opens over HTTPS.
Common mistakes:
- White screen for an SPA on a sub-path: assets are looked up from
/, but the site lives at/repo-name/. Fix it with a relative base: in Vite —base: "/repo-name/"(or"./") invite.config; in CRA —"homepage"inpackage.json. Rebuild and re-upload. - 404 when you reload an SPA route: Pages knows nothing about client-side routing. Make
404.htmla copy ofindex.html, or use HashRouter. - The repository is private → on the free plan Pages may not be available. Make it public.
🧠 For the senior: cleaner is deploying via GitHub Actions (
actions/deploy-pages) from a build artifact rather than "a folder out of main"; custom domain + automatic HTTPS in the Pages settings; cache busting via hashes in file names (Vite does this out of the box).
Step 2 (Route B). Netlify — the easiest for SPAs
What we're doing: publishing a folder through the CLI (or literally by dragging it into the browser).
npm install -g netlify-cli # install the Netlify CLI
netlify login # log in through the browser
netlify deploy --dir=dist # draft deploy (preview link); replace dist with your build folder
netlify deploy --prod --dir=dist # production deploy to the main address
On the first deploy, Netlify offers to create a new site or pick an existing one.
How to tell it worked: the CLI prints a Website URL (with --prod, the production address) — open it, the site works over HTTPS. Without logging in you can run netlify deploy --prod --dir=dist --allow-anonymous.
💡 No terminal at all: on app.netlify.com just drag the build folder into the drag-and-drop zone — you get a link in seconds.
Common mistakes:
- Pointed
--dirat the wrong folder (sources instead ofdist/build) → an empty/broken site. The path must point at the compiled build. - 404 on SPA routes: add a
_redirectsfile to the publish folder with the line/* /index.html 200(Netlify reads it and serves the SPA on any path). - Forgot
--prod→ you deployed a preview, not the main address. Production only happens with the--prodflag.
Step 2 (Route C). Cloudflare Pages — a fast global network
What we're doing: uploading the folder directly through Wrangler (Cloudflare's CLI).
npm install -g wrangler # install Wrangler
npx wrangler login # log in through the browser
npx wrangler pages project create # create a Pages project (once, pick a name)
npx wrangler pages deploy dist # publish the build folder (replace dist with yours)
How to tell it worked: Wrangler prints a link like https://<project>.pages.dev — open it, the site is served over HTTPS from a global CDN.
Common mistakes:
- Not logged in / no Cloudflare account → run
wrangler loginfirst. The account is free. - Pointed
pages deployat the wrong folder → the wrong thing goes live. The argument is the build folder (dist/build). - SPA routes 404 → drop in a
_redirectsfile (same as Netlify:/* /index.html 200) or configure a fallback in the project.
Step 3. Check it and attach a domain (optional)
What we're doing: making sure everything works and, if you want, putting your own domain on it.
- Open the link you were given on your phone and on someone else's device — the site loads, HTTPS (the padlock) is there, pages and routes work.
- Your own domain: all three platforms have a "Custom domain" section — you add the domain and create the DNS record they tell you to; the platform issues the HTTPS certificate itself.
How to tell it worked: other people can open the site by the link (and by your domain, if you attached one), HTTPS everywhere, navigation doesn't throw 404s.
Common mistakes:
- "It opens for me but not for others" — you were testing from cache/locally. Open it in incognito and ask someone else to try.
- The domain "didn't pick up" right away — DNS doesn't update instantly (minutes to hours). Double-check the record against the platform's instructions and wait.
🧠 For the senior
- CI/CD instead of manual deploys: connect the repository to Netlify/Cloudflare Pages — every
git pushbuilds and deploys automatically (a preview per branch, prod on main). On GitHub Pages — Actions withdeploy-pages. - SPA fallback and headers:
_redirects/_headers(Netlify/CF) for history routing, cache policies and security headers (CSP, HSTS). Immutable cache on hashed assets,no-cacheonindex.html. - Base path: when deploying to a sub-path, set
base/publicPathcorrectly; on Netlify/CF served from the root you usually don't need it. Check that absolute asset paths don't drift. - Previews and rollback: all three give you immutable deploys and instant rollback to a previous version — use it for safe releases.
- The edge of static: if you need a backend piece (forms, auth, secrets), add serverless next to the static files (Netlify Functions / Cloudflare Workers) instead of dragging everything onto a separate server for one small thing.
⚠️ Warnings
- A public repository and public static files = public code. Before deploying, make sure the files and the git history contain no secrets, keys, tokens or private data — all of it is visible to everyone in the page source. A leaked key = a breach.
- No "secret" backend keys in JS. Any key that ends up in static files is available to anyone through DevTools. Server secrets belong on the server / in serverless variables, not in frontend code.
- Static only. Don't try to "hide" a backend/database in here — there isn't one. For server-side logic use a separate server (see the corresponding guide).
- Free-tier limits. The platforms have traffic/build limits on the free plan — plenty for personal and small projects, but check the pricing if you expect load.
🆘 Rescue prompt
Help me put my site on free hosting, explain it step by step.
WHAT I HAVE: <plain HTML/CSS/JS / a compiled React-Vite build / sources, not built yet>.
HOSTING: <GitHub Pages / Netlify / Cloudflare Pages / recommend one for my case>.
MY OS: <macOS / Windows>. I'M STUCK ON: <white screen / 404 on routes / can't tell which folder to deploy / CLI error>.
What to do:
1) Tell me whether I need to build and which folder to publish (dist/build vs sources).
2) Give me the exact commands for the chosen host, step by step.
3) If it's an SPA — how to fix the base path and 404s on routes (base / _redirects / 404.html).
4) How to verify others can open it over HTTPS. Warn me about secrets in public code.
💎 Depth and value
- "Done on my machine" ≠ "people have it". As long as the site only lives locally, it effectively doesn't exist. Free hosting with HTTPS turns a folder of files into a product behind a link in a couple of minutes — the last excuse, "I have nowhere to put it", disappears.
- Three routes, one model. GitHub Pages, Netlify and Cloudflare differ in which buttons you press, but the essence is identical: "build the static files → upload → get an HTTPS link". Once you get the model, you can deploy on any of them and you're not locked into one vendor.
- A step toward more. Once you've got static hosting down, you naturally grow into CI/CD (auto-deploy on push), your own domain, serverless functions and — when you actually need a backend — your own server. This is the first and cheapest step of "showing it to the world", and everything else starts here.