War story: the deploy that served nothing useful

You built a clean site. Locally, dist/index.html looks perfect. You compress dist, upload to StaticHost, open the HTTPS preview, and get a 404—or a blank listing that is not your homepage. Panic suggests “the host is broken.” Almost every time, the archive contains one extra directory layer.

This guide is about that failure mode: nested folders inside zip files, how to recognize them, how to pack correctly, and how to separate nesting bugs from the *other* 404 class (SPA history routes under try_files $uri $uri/ =404).

What the host actually mounts

StaticHost serves the root of what you uploaded as the site root. Visitors request /. The server looks for /index.html (or an equivalent index). If your zip expands to:

my-project/
  dist/
    index.html
    assets/…

and you zipped my-project, the site root may contain only my-project/—no index.html at /. Requesting / fails. Requesting /my-project/dist/ might work, but that is not the URL you wanted to print on a business card.

Even zipping the dist folder from its parent can encode a top-level dist/ directory inside the archive:

dist/
  index.html

Then / is wrong and /dist/ is accidentally right. The fix is to zip *contents*, so the archive root is index.html plus siblings.

How to zip contents correctly

Linux / macOS (terminal), inside the build output directory:

cd dist   # or build, public, _site, out
zip -r ../site-upload.zip .

The . matters: it includes files in the current directory without wrapping them in a named parent.

Verify before upload:

unzip -l ../site-upload.zip | head

You want to see index.html near the top-level paths, not only dist/index.html.

macOS Finder. Compressing a selected folder always nests that folder’s name. Instead, open the folder, select all files (Cmd+A), then compress the selection. Confirm the archive structure by unzipping to a temporary location once.

Windows. Same principle: enter the output folder, select all files and subfolders, send to compressed zip. Do not zip the folder icon from the parent view if you need a flat root—unless you intentionally want that folder name in URLs.

GitHub deploys have an analog

If the repository root is source code and index.html lives only under dist/, and StaticHost serves the repository root, you get the same class of bug without a zip. Serve a branch whose root *is* the artifact, or arrange CI to publish that layout. The platform will not cd dist for you or run npm run build. See git deploy a static site and host a dist folder online.

Nested folder versus SPA 404

SymptomLikely cause
/ 404 or empty; /something/index.html worksExtra directory in archive or repo root
/ works; /about 404 for a client-only routeExpected: no SPA fallback; use hash or SSG files
/styles.css 404 while HTML loadsWrong relative paths *or* CSS left outside the zipped set

Do not “fix” a nesting bug by asking for nginx rewrites. Do not “fix” an SPA deep-link 404 by re-zipping. Diagnose which row you are in. SPA detail: host a single-page application.

A deliberate preflight checklist

  1. Build or save files into output/.
  2. cd output and list: confirm index.html.
  3. Create zip from . or select-all compress.
  4. unzip -l and confirm top-level index.html.
  5. Upload to StaticHost; open HTTPS preview at /.
  6. Hard-refresh; check Network for CSS/JS 404s.
  7. Keep deploy history handy for rollback if the new zip is worse.

Partial content zips

Another war-story variant: you selected only index.html and forgot assets/. Homepage HTML loads with missing bundles. The zip root is correct; the tree is incomplete. Always zip the full output directory contents after a clean build.

Large media sites should watch plan storage: Starter 2 GB, Pro 10 GB, Scale 30 GB, Business 100 GB ($9 / $30 / $65 / $130). Short trial ~1 day. No forever-free, no cPanel, no email, no built-in CDN, no remote Hugo/npm.

Recovery on a live wrong zip

Roll back to the last good deploy if visitors are hitting production. Rebuild the archive correctly. Deploy again. If DNS already points at you, preview still helps verify before you announce, but production will follow the new deploy—so get the zip right.

For custom domains, nesting bugs look the same as on preview; TLS is unrelated. Certificate issuance after DNS verify will not repair a missing root index.html.

A realistic week shipping zip a static site without 404

Monday starts with a broken relative CSS path that only appears once the files leave a designer’s laptop. You flatten the zip so index.html sits at the archive root, redeploy to StaticHost, and HTTPS preview finally matches local intent. Preview HTTPS is available immediately; custom-domain certificates issue only after DNS verifies.

Wednesday adds a custom domain. DNS is created exactly as the dashboard specifies; verification lags an hour; the certificate appears afterward. Someone asks whether email is included—you point them at a mail vendor and keep MX untouched. There is a short trial (~1 day), not a forever-free tier; no cPanel, no email, no built-in CDN product.

Thursday an engineer enables history-mode routing for a “cleaner URL.” Refresh on a nested path 404s. You revert to hash routing for the week’s deadline and schedule prerender work properly instead of inventing an nginx rewrite StaticHost will not apply. Deploy history supports rollback when a publish goes wrong.

Friday staging on Pro receives a build with staging API URLs baked via public env prefixes. Production stays on last week’s known-good deploy until Monday’s review. Rollback remains one click if marketing’s late copy drop breaks mobile layout. nginx behaves like try_files $uri $uri/ =404 with no SPA index fallback.

WordPress/PHP needs a different kind of host.

That week is what zip a static site without 404 looks like when the host stays boring and the team respects file truth.

FAQ

Should I include node_modules in the zip?

No for typical apps. Publish build output only.

Can I use tar.gz?

Use the archive format the StaticHost upload UI accepts (zip is the common path described here). If only zip is supported in the UI, use zip.

Why did Finder make a zip that looks right but still nests?

You compressed the folder from the parent view. Compress selected contents inside the folder instead, or use the terminal method.

Does base href fix nesting?

A <base> tag can paper over some asset paths; it does not invent a root index.html. Fix the archive.

Is this related to Vite base?

Wrong Vite base causes asset 404s even with a correct zip. Different bug. See host a Vite build online.