hosting

How to Host a ZIP File as a Website

By PreviewSend Team · Jul 14, 2026 · 8 min read

You exported a site and ended up with a ZIP. Maybe it's a dist folder from a build, a single HTML page a chatbot wrote for you, or a template you bought that shipped as an archive. The files are done. What you're missing is a URL — somewhere those files live so you can send a link instead of an attachment nobody can open.

The good news: a ZIP of static files is the easiest thing on the web to host. The catch is that a few small structural mistakes will get an archive rejected or leave you staring at an unstyled page, and they're the same mistakes every time. This walks through what a host actually needs, where things break, and how to go from archive to live link.

What counts as a static site

A static site is any site a server can hand over as-is, without running code to build the page first. Open the archive: if you see .html, .css, .js, images, and fonts — and nothing that expects a database or a running backend — you have a static site.

Most modern web projects end up here even when they didn't start that way. A React or Vue app compiles down to static files. A framework like Vite, Astro, or Next.js in export mode produces a folder of HTML and bundled JavaScript. The app still does plenty in the browser, but the hosting is dumb: fetch file, return file. That's why a ZIP is enough.

What isn't static: anything that runs on the server per request — a PHP app, a Rails backend, a Node server that renders pages on the fly, or an app whose database lives on the same box. Those need a runtime, not a file host. If your project has a backend but a static frontend that talks to it over an API, you can still host the frontend this way; it just keeps calling the same API URL it always did.

Get the ZIP structure right

There is exactly one structural rule, and nearly every failed upload breaks it: index.html has to be at the root of the archive.

When a host unzips your file, it looks for index.html to know what to serve at /. If your archive unzips to a single wrapper folder — my-site/index.html instead of index.html — the host opens the ZIP, finds a folder and no entry point, and either errors out or serves a directory listing.

This is the number-one gotcha because of how the OS "Compress" command works. Right-click a folder and compress it, and you get an archive containing that folder. What you want instead is to open the folder, select everything inside, and compress the selection:

cd dist
zip -r ../site.zip .

The . means "the contents of this directory," so index.html lands at the top of site.zip. On macOS or Windows, do the equivalent: enter the folder, select all, then Compress or Send to → Compressed folder.

Some hosts, PreviewSend included, will rescue a nested index.html. We confirmed this against PreviewSend's parser (src/lib/zip.ts): it prefers a root-level index.html and otherwise falls back to the shallowest one anywhere in the archive. Useful as a safety net — but zipping the contents directly is the layout that works everywhere, so don't build a habit on the fallback.

Where the build output actually is

If you built the site yourself, the files you want to zip aren't your source code — they're the compiled output. Different tools put that output in different folders, which trips people up when they zip the project root and wonder why the host sees raw .jsx files.

The common ones:

  • dist/Vite's default build output and Astro's too.
  • build/ — Create React App and some others.
  • out/ — Next.js static export.
  • public/ — a few site generators, though this name also means "source assets" in other tools, so check.

Run your build (npm run build for most JS projects), then look for the folder that contains a fresh index.html and a hashed assets/ or _next/ directory. That folder's contents are what you zip. If you're not sure which folder is the output, the one with minified filenames like index-a1b2c3.js is the built one.

The relative-path trap

You upload the ZIP, the link works, and the page loads — as raw HTML with no styling, no images, broken JavaScript. This is the most common "it doesn't work" report, and it's almost never the host's fault.

The cause is absolute paths. If your HTML says:

<link rel="stylesheet" href="/assets/app.css" />

the leading / tells the browser to fetch app.css from the very top of the domain. That's correct when your site owns the whole domain root. It's wrong the moment your site is served from a subpath — example.com/my-project/ — because the browser looks for example.com/assets/app.css, which doesn't exist.

Two fixes:

  • Use relative paths. href="assets/app.css" (no leading slash) resolves next to the current page, wherever that is.
  • Tell your build tool the base path. Vite exposes a base option for exactly this — set it to the subpath you'll deploy under and it rewrites every asset URL for you. Astro and most bundlers have an equivalent.

PreviewSend handles the subpath case for you. Its serving layer injects a <base href> tag pointing at the served document's real directory (src/lib/preview/base-href.ts), so relative references resolve correctly even on nested SPA routes — and it leaves your own <base> tag alone if you've already set one. Absolute-from-root paths are still on you to fix.

Where you can host it

Once the ZIP is well-formed, you have real choices. A few that take a folder or an archive directly, no Git required:

  • Cloudflare Pages has a Direct Upload mode with a "Drag and drop your files" box that accepts a ZIP or a folder. It's fast and free, capped at 1,000 files per drag-and-drop deploy, and it publishes to a pages.dev URL.
  • Netlify Drop lets you drag a build folder or ZIP file onto a drop zone and get a live link. You can update the same project later by dropping in a new build.

Both are solid choices for a permanent public site. A private draft takes more setup: you have to configure access control and indexing behavior separately, and neither service automatically adds a noindex response header to every page you upload.

That's the gap PreviewSend fills. The flow is short:

  1. Create a free PreviewSend account — no card, and the free plan hosts a live site.
  2. Create a project and upload site.zip. It validates the archive, extracts it, and gives you a stable URL right away.
  3. Share the link. Every project is private-leaning by default: a long random slug plus a noindex, nofollow header and a robots.txt disallow, so it stays out of search results with no configuration.

From there you can add a password, set an expiry date so the link stops working after a deadline, or point a custom domain at it on the paid tiers. The URL doesn't change when you do any of that.

If your ZIP came out of an AI builder specifically, two companion walkthroughs go deeper on the export step: one on taking a Lovable project's build off Lovable's own hosting, and one on putting a Claude artifact behind a password. Both funnel into the same upload flow described here.

Keeping a hosted draft out of search

Hosting a ZIP publicly and keeping it out of Google are two separate problems, and the second one catches people off guard when a half-finished draft turns up in search results.

The reliable control is a noindex directive, delivered one of two ways per Google's indexing docs: a <meta name="robots" content="noindex"> tag in the page <head>, or an X-Robots-Tag: noindex HTTP response header. On a plain static host you'd add the meta tag to every page yourself.

The trap is reaching for robots.txt instead. A robots.txt disallow blocks crawling, not indexing — and Google is explicit that a disallowed page can still be indexed if it's linked from somewhere, since the crawler never sees the noindex you were counting on. Use both together (as PreviewSend does, sending the header on every response so there's nothing to remember), but don't lean on robots.txt alone.

Updating the same URL

The reason a hosted URL beats a re-shared ZIP is that the link outlives the file. Push a change and everyone who already has the link sees the new version — no re-sending.

On PreviewSend, updating means uploading a new ZIP to the same project. It becomes the active version, the URL is untouched, and previous versions stay on file so you can roll back if the new build breaks something. That's the practical difference between hosting a ZIP and just sharing one: the address is stable, and the contents behind it are yours to swap out whenever the site changes.

FAQ

Can I really host a website straight from a ZIP file?

Yes, as long as the ZIP contains a static site — plain HTML, CSS, JS, and assets with no server code. The host unzips it and serves the files. The one rule that matters is that an index.html sits at the root of the archive.

My page loads but the CSS and images are missing. What went wrong?

Almost always an absolute path problem. If your HTML references /assets/app.css with a leading slash, it resolves against the domain root instead of your site folder. Switch to relative paths (assets/app.css), or set your build tool base option to match where the site is served.

Should I zip the folder or the files inside it?

Zip the contents, not the wrapping folder. If index.html ends up one level deep — inside dist/ inside the archive — some hosts refuse it. Open the build folder, select everything inside, and compress that.

Will a hosted ZIP show up in Google?

That depends on the host. Public static hosts index by default unless you add a noindex tag yourself. PreviewSend serves every site with a noindex header and a robots.txt disallow from the start, so a shared draft stays out of search results without any extra setup.

Have a site to host? Upload it and get a live link in seconds.

Get it free

← Back to the blog