Think in three layers of files
Publishing a frontend is not mysterious if you separate concerns:
- Documents — HTML files that define structure and point at assets.
- Presentation — CSS (and fonts referenced from CSS).
- Behavior — JavaScript modules or bundles, plus any JSON the client loads.
StaticHost publishes all three the same way: as files in a tree served by nginx with try_files $uri $uri/ =404. There is no special “JS hosting mode.” A .js file is just another object at a URL. That is liberating for simple sites and strict for SPAs that expect path fallbacks.
This article is file-oriented on purpose. You will map local paths to URLs, avoid the usual reference mistakes, and publish with zip or GitHub without asking the platform to build anything.
Map paths before you publish
Suppose this local layout:
publish-root/
index.html
styles/main.css
scripts/app.js
assets/logo.svg
After a correct publish, these URLs should work on the preview host:
/or/index.html/styles/main.css/scripts/app.js/assets/logo.svg
In index.html, references should look like:
<link rel="stylesheet" href="/styles/main.css" />
<script type="module" src="/scripts/app.js"></script>
<img src="/assets/logo.svg" alt="" />
Root-absolute paths (/styles/...) are robust when HTML lives in subfolders. Relative paths (styles/main.css) are fine for flat sites but break if you move HTML into /blog/post.html without updating them. Pick a convention and stick to it across the project.
Subresource integrity attributes, defer/async on scripts, and type="module" all work as in any static server—StaticHost does not strip them. It also does not transpile modern JS for old browsers; that is your build step’s job if you need it.
CSS and font specifics
CSS @font-face URLs resolve relative to the CSS file’s location, not the HTML’s. If styles/main.css says url(../assets/fonts/Inter.woff2), the font must exist at assets/fonts/Inter.woff2 in the published tree. Missing fonts often show as 404s nobody notices until design QA.
Avoid file:// leftovers and hard-coded http://localhost in stylesheets. On HTTPS production, mixed content rules will block active content and warn on passive content. Details: static hosting with free SSL.
JavaScript specifics
ES modules (import/export) require correct MIME serving and exact relative URLs between modules. If app.js imports ./lib/util.js, that file must be published beside it. Bundlers collapse this into fewer files; hand-written module graphs need every file present.
Client-side routers that use the History API need either:
- hash routes (
/#/settings), or - a generated HTML file for each path you care about,
because unknown paths 404. There is no platform rewrite to index.html. See static hosting for JavaScript apps and host a React build online.
Never publish private API keys in JS. Publishable keys only, constrained at the provider.
Build outputs versus hand-written files
If you use Vite, webpack, or Parcel, run the production build locally and publish dist/build. Source maps: include them only if you want them public. Environment variables must be baked at build time on your machine or CI; the host will not inject them. Vite notes: host a Vite build online.
Hand-written trees skip the build, but you still zip carefully. The archive root must be the publish root. Nested-folder 404s are covered in zip a static site without 404.
Publish procedure
- Validate the tree locally (open pages, check console for failed loads).
- Zip contents or push the artifact branch to GitHub.
- Deploy on StaticHost; open HTTPS preview immediately.
- In Network panel, filter by CSS and JS; fix any 404 and redeploy.
- Use deploy history to roll back if a “small CSS tweak” removed a file.
- Attach a custom domain after DNS verifies and the certificate issues.
Plans: Starter $9/mo (1 site, 2 GB), Pro $30 (3 sites, 10 GB, staging), Scale $65 (10 sites, 30 GB), Business $130 (30 sites, 100 GB, teams). Short trial ~1 day. No cPanel, no email, no built-in CDN, no remote npm.
Caching and update discipline
Browsers cache static assets aggressively when headers and filenames allow. Bundlers that fingerprint main.a1b2c3.js make updates easy: HTML points at a new name, old caches do not matter. Hand-written app.js without fingerprinting may leave clients on old behavior until they hard-refresh. When you publish unfingerprinted JS, expect support questions; consider query busting (app.js?v=2026-08-31) as a manual compromise.
HTML should usually be revalidated more eagerly than fingerprinted assets. You do not control every header on every plan detail in this article; you do control whether filenames change when content changes.
What this workflow excludes
Publishing HTML/CSS/JS here does not give you server-side session storage, PHP includes, or WordPress themes. Those need an application host. It also does not run Hugo or Jekyll for you—emit files first, then publish.
For a deploy-centric twin of this guide, read how to deploy an HTML website. For hosting framing, how to host a frontend website.
Worked example: flat site to preview in fifteen minutes
mkdir publish-root && cd publish-root
printf '%s\n' '<!doctype html><meta charset=utf-8><title>Hi</title><link rel=stylesheet href=/styles/main.css><h1>Hi</h1><script type=module src=/scripts/app.js></script>' > index.html
mkdir -p styles scripts assets
echo 'body{font-family:system-ui;margin:2rem}' > styles/main.css
echo 'console.log("ok")' > scripts/app.js
zip -r ../publish.zip index.html styles scripts assets
unzip -l ../publish.zip | head
Upload publish.zip to StaticHost. Open HTTPS preview. Network panel should show /styles/main.css and /scripts/app.js at 200. If you accidentally zipped the parent folder, / 404s until you re-pack with index.html at the archive root—zip guide.
Failure table (HTML/CSS/JS publish)
| Symptom | Likely cause | Fix |
|---|---|---|
CSS 404 after moving HTML into /blog/ | Relative hrefs broke | Prefer root-absolute /styles/… or update relatives |
| Fonts missing | @font-face path wrong vs CSS location | Fix URL relative to the CSS file |
| Module import fails | Missing file or file:// testing | Publish sibling modules; test on HTTPS preview |
| Deep link 404 | History router without files | Hash routes or emit HTML per path |
Clients stuck on old app.js | Unfingerprinted cache | Query bust or fingerprint; redeploy |
| Mixed content warnings | http:// asset URLs | Use HTTPS or self-host; redeploy |
StaticHost does not transpile TypeScript, run npm, or process forms server-side. Plans: Starter $9 (1×2 GB), Pro $30 (3×10 GB + staging), Scale $65, Business $130. Trial ~1 day; no forever-free, email, cPanel, or built-in CDN product. Order for domains: HTTPS preview → DNS verify → certificate.
FAQ
Can I publish TypeScript files directly?
Browsers do not execute .ts as modules in the usual static setup. Compile to JavaScript first, then publish the JS.
Do I need a bundler for a small site?
No. A few HTML files with one CSS and one JS file are a valid publish unit. Bundlers help as dependency graphs grow.
Why is my module import failing with CORS or MIME errors?
Usually the import path 404s or you opened the HTML as file://. Test on the HTTPS preview origin after publish.
Can CSS @import pull from another origin?
Yes if that origin allows it; for reliability, host critical CSS with your site files.
How do I publish only changed assets?
Rebuild or update the full tree and redeploy. Partial ad-hoc overwrites invite drift; full-tree deploys plus rollback stay coherent.