Custom Apps

How to Deploy a Static Site to Your Own VPS

The build runs on the server, the output is served by Nginx without root, and the site lives on hardware you rent rather than a platform that profiles your visitors.

A static site does not need a Node process running forever. It needs a build, and then a web server handing out files. The node_npm_static strategy does both: npm builds your project on the server, and the output is served by unprivileged Nginx, with the single page fallback turned on by default.

The reason to do this instead of using a free static host is ownership. The files sit on a VPS you rent, under your domain, with the option of an onion address, and no third party sits between the visitor and the page.

What you get

  • Your npm build script run on the server, from your lockfile
  • Unprivileged Nginx serving the output folder, port 8080 by default
  • SPA routing by default: unmatched paths fall back to index.html
  • Multi page mode when you want a real 404 instead of a fallback
  • Your own domain, a free subdomain, or an onion address

The three settings that matter

static_output_dir is the folder your build produces, relative to the project. The default is dist. Use build, out, public/site or whatever your framework writes, up to five path segments. The folder has to contain index.html when the build finishes, and the recipe rejects hidden folders, parent segments, node_modules and symlinks.

static_spa decides what happens to a path that matches no file. Left on, which is the default, every unmatched request returns index.html and your client side router takes over. That is right for React, Vue and Svelte apps. Turn it off for a documentation site or a blog, where a wrong URL should answer 404 rather than render the home page with a broken route.

public_build_vars carries values into the build, restricted to VITE_, NEXT_PUBLIC_ and PUBLIC_ names, up to 20 keys.

A static site has no runtime configuration

Values are frozen into the bundle when it is built. Changing a deployment variable afterwards does nothing to the files already generated, and there is no server side process to read a new value. If a setting changes, rebuild. This surprises people moving from a server rendered app, and it is the reason public_build_vars exists as a separate input.

Deploy it

Check the build script and the output folder

npm run build has to produce index.html inside the folder you will declare. Run it locally once and look at the directory name, because dist, build and out are all common defaults and the wrong one fails the build with a clear message.

Create the deployment

From My Apps in Advanced view, the CLI, the REST API or your AI agent, create a custom deployment with build strategy node_npm_static, your Git URL or uploaded source, and static_output_dir if it is not dist. Set project_dir when the site lives in a subfolder of the repository.

Choose SPA or multi page

Leave static_spa on for a single page app. Turn it off for a content site, so a missing page answers 404 instead of quietly serving the shell.

Pass build values, not secrets

Put the API base URL, the analytics flag or the public key in public_build_vars. Anything that must stay private cannot live in a static site at all: the browser downloads everything.

Point your domain and check it

Attach your domain or take the free subdomain, then load a deep link directly, not only the home page. That is the request that proves your fallback choice is right.

Publish the same build as an onion site

A static build is the easiest thing to mirror on Tor, because there is no backend to leak a hostname or an outbound connection. See publishing a Tor onion site, and review third party fonts, analytics and CDN links first: an onion address does not hide requests your page makes to someone else’s server.

Review branches before they go live

Eligible Git branches can get a temporary preview address so a change is reviewed before it reaches the public site. See previewing Git branches over Tor. Build secrets are never inherited by previews, by design.

Keeping it updated

Connect automatic deploys on git push and every merge rebuilds the site. Redeploys keep the output folder and routing mode you saved, so later releases are one action.

Static deployments are files, not a service, so the health policy used by the server recipes does not apply here. What you verify instead is the page itself: load a deep link, check the assets resolve, and confirm the 404 behavior matches the mode you chose.

Start now

Get an offshore VPS with the agent preselected, read the recipe reference at docs.imprezahost.com, or look at the server side recipes: Node, Python and PHP.

Ready to build privacy-first?

No KYC, no email required, crypto payment. Deploy an offshore server in minutes, or do it all by chat with the Impreza agent.