JavaScript apps are still files at the origin
A JavaScript application—React, Vue, Svelte, Lit, Alpine-enhanced pages, vanilla ES modules—ships as .js, .css, and .html (plus assets). StaticHost hosts those files. The app’s brain runs in the visitor’s browser and calls APIs on other origins. That split is the whole idea.
What StaticHost does not provide: Node to execute your app server-side, remote npm install, platform functions, or nginx soft-fallback for client routers. try_files $uri $uri/ =404 remains the law. Preview HTTPS is immediate; custom domain certificates arrive after DNS verifies. Plans are Starter $9, Pro $30, Scale $65, Business $130 after a ~1 day trial—no forever-free, no email, no cPanel, no built-in CDN product.
If that paragraph feels constraining, good: it is better to feel constrained before you migrate than after cutover.
Pick a shipping shape
| Shape | Server paths | Fit on StaticHost |
|---|---|---|
| Multipage HTML + sprinkles of JS | Real HTML files | Excellent |
| SPA + hash router | / + #/… | Good |
| SPA + history router + prerendered files | Real files per route | Good |
| SPA + history router, no files for routes | Missing paths 404 | Bad here |
SSR app (next start, etc.) | Needs Node | Wrong host |
Framework deep dives: React, Vue, Vite, Svelte static, Next static export, SPA overview.
Build and publish loop
npm ci
npm run build
cd dist # or build/ or out/
zip -r ../js-app.zip .
unzip -l ../js-app.zip | head
Upload or use a GitHub artifact branch—git deploy a static site, host a dist folder online. Verify on HTTPS preview. Roll back from history. Attach a custom domain only after the preview behaves.
Environment variables belong only in public prefixes (VITE_, REACT_APP_, PUBLIC_, NEXT_PUBLIC_) and bake in at build time. Grep the bundle for accidental secrets before publish:
grep -R "SECRET\|API_KEY\|private_key" dist || true
If something sensitive appears, rotate credentials and rebuild with a corrected env strategy. StaticHost cannot un-bake a secret from a previous deploy that browsers already cached—roll back and invalidate carefully on your side (service workers especially).
Worked example: Vite vanilla TS app with hash routes
npm create vite@latest widget-app -- --template vanilla-ts
cd widget-app
npm ci
In the router or navigation code, prefer hash-based locations if you need multi-screen UX without prerender:
// minimal illustration — navigate via hash
location.hash = '#/settings'
npm run build
cd dist && zip -r ../../widget.zip .
Deploy widget.zip. Open preview. Hard-refresh on /#/settings—works. Hard-refresh on /settings without a file—404 by design. That contrast is the product teaching you how nginx is configured.
Browser platform concerns (still yours)
- CORS on APIs for both preview and production hosts
- Auth via external IdPs; no server sessions on this origin
- Storage (
localStorage/ IndexedDB) caveats across subdomains - Service workers caching old deploys after a rollback
- Module paths all present on the server (no transform-on-request)
Publishing JS without a bundler is valid for small apps—how to publish HTML, CSS, and JavaScript files—as long as every imported file exists at its URL.
Performance without a CDN product
Code-split large apps; do not celebrate a 3 MB single bundle on a host without a built-in CDN product. Craft notes: fast static site hosting, lightweight high-performance static hosting. Measure on a throttled mobile profile. Cache-bust hashed filenames from your bundler; avoid infinite-cache service workers until you have an update strategy.
Plans
Starter $9 (1×2 GB), Pro $30 (3×10 GB + staging) for staging builds with staging API URLs, Scale $65 (10×30 GB), Business $130 (30×100 GB + teams). Trial ~1 day. No PHP/WordPress runtime here.
When not to put a JS app on StaticHost
- You need SSR for SEO on personalized pages every request.
- You need server-side sessions sticky to this origin.
- Your router strategy is history mode and you refuse prerender or hash routes.
- You expected remote npm builds as part of “git push.”
Failure table (JS apps)
| Symptom | Likely cause | Fix |
|---|---|---|
Blank page, 404 on /assets/index-….js | Nested zip or wrong base | Re-zip dist contents; set Vite base: '/' |
| API works locally, fails on preview | CORS allowlist missing preview host | Add preview and production origins |
| Users see old bundle after fix | Service worker or long cache | Bump SW version; hashed assets help |
| Deep link 404 | History router without files | HashRouter, prerender, or different host |
| Secret in client bundle | Env without public prefix discipline | Rotate; rebuild; stop inlining secrets |
Extra procedure: map an ES module graph without a bundler
Small apps can ship raw modules if every file exists at its URL:
app/
index.html
main.js
lib/math.js
lib/dom.js
main.js:
import { sum } from './lib/math.js'
import { qs } from './lib/dom.js'
qs('#out').textContent = String(sum(2, 2))
Zip app/ contents, deploy, open HTTPS preview. If lib/math.js 404s, the graph breaks—there is no transform-on-request. Bundlers collapse this problem; hand-written graphs demand discipline. Either way, history-mode client routes still need files or hashes because nginx answers =404 for missing paths.
Second worked path: promote a fingerprinted bundle
- Build with the bundler so filenames include content hashes.
- Grep
distfor secrets before publish. - Deploy; verify on preview.
- Roll back once on purpose so the team knows the control.
- Only then DNS → verify → certificate.
Plans: Starter $9, Pro $30 (staging builds with staging API URLs), Scale $65, Business $130. No forever-free after ~1 day trial; no email; no cPanel; no built-in CDN product; no PHP/WordPress runtime. Performance notes: lightweight high-performance static hosting.
Failure-adjacent procedure: CORS and preview origins
When the UI loads but API calls fail only on StaticHost preview:
- Note the exact preview origin from the address bar.
- Add it to the API allowlist alongside the future production hostname.
- Rebuild only if the API base URL itself was wrong; CORS fixes live on the API.
- Retest login/token refresh on preview HTTPS.
- After DNS cutover, confirm the custom origin is also allowlisted—certificate timing does not fix CORS.
StaticHost will not proxy your API or invent server sessions. The browser talks to your API; the origin merely serves JS. Keep that split loud in design reviews so nobody “temporarily” puts secrets into the bundle.
FAQ
Is a JS app “dynamic” or “static”?
The origin is static; the UI can be dynamic in the browser.
Can I use WebSockets?
From the browser to a WebSocket server elsewhere, yes. StaticHost is not that server.
Do I need TypeScript compiled?
Yes—publish JS (or WASM), not raw project TS expecting a transform on the server.
Why do imports fail only on deep routes?
Base href / router base / asset path issues, or you expected SPA fallback. Check the Network panel.
Should small businesses use JS apps for brochures?
Often overkill—static hosting for small business. Use JS when interactivity needs it.