Static sites and front-end builds
Publish plain HTML or build React, Vue, Vite, Angular, Astro, Gatsby or Docusaurus sites from Git, with precompressed assets, caching, rollback and SPA routing.
A static site is a folder of HTML, CSS, JavaScript and media files that nginx serves directly, with no application process. Use it for plain HTML pages and for front-end frameworks that compile to files (React, Vue, Svelte, Angular, static Astro or Next.js exports, documentation generators). If your project needs a server at runtime, see Node.js apps instead.
Choose how to publish
| Method | When to use | How it works |
|---|---|---|
| HTML website | Hand-written or pre-built files, no build step | Upload the files to domains/<domain>/public_html. |
| Git deploy | A repository, with or without a build step | ZoPanel clones, builds and publishes each release, with rollback. |
| Deploy tab with Uploaded files (File Manager) | Source code without Git | Upload the project to domains/<domain>/source, then deploy. It is built like a Git release. |
Publish plain HTML files
- Open Websites → New website, enter the Domain and choose HTML.
- Keep Free SSL (Let's Encrypt) on and click Create website.
- Upload your files to
domains/<domain>/public_htmlwith the File Manager or SFTP. The home page must beindex.html(orindex.htm).
Changes are live as soon as the files are saved. PHP is not run on these websites: requests for .php files are refused with 403.
Deploy a front-end build from Git
- Open Websites → New website, choose Git deploy, enter the Repository URL, Branch and, for monorepos, the Root directory.
- Click Create website. ZoPanel installs dependencies, runs the build and serves the output folder.
- Open the Deploy tab to see the detected settings in Build & run: Type is Static site and Output directory is the folder that is published.
You can also connect an existing HTML website to Git from its Deploy tab. After the first deployment, the website serves the build output instead of public_html.
What is detected
With Auto-detect on, ZoPanel reads package.json and picks the output folder:
| Dependency in package.json | Detected as | Output directory |
|---|---|---|
vite (and no start script) |
Vite (React/Vue/Svelte) | dist |
react-scripts |
Create React App | build |
@angular/core |
Angular | read from angular.json (outputPath + /browser), else dist |
gatsby |
Gatsby | public |
@docusaurus/core |
Docusaurus | build |
vitepress |
VitePress | .vitepress/dist |
astro without @astrojs/node |
Astro | dist |
@sveltejs/kit with @sveltejs/adapter-static |
SvelteKit | build |
next with output: 'export' in next.config.js, .mjs or .ts |
Next.js | out |
The build command is npm run build (or pnpm/yarn run build when their lockfile is present) and the install command follows the lockfile, as for Node.js apps. If package.json has no build script, the note "No build script found in package.json" appears.
Without package.json (and no PHP, Python, Ruby, Go, Java or .NET project files), a repository with index.html at its root is served as Static HTML from the root directory. Anything else is served as static files with the note "No known framework detected; serving files as a static site."
Other generators
Hugo, Jekyll and other generators that are not npm packages are not installed on the server. Build the site in CI (or locally) and push the generated folder, for example to a deploy branch, then deploy that branch. For npm-based generators that are not in the table (Eleventy, Nuxt with nuxi generate…), set the settings yourself:
- On the Deploy tab, turn Auto-detect off in Build & run.
- Set Type to Static site.
- Set Build command, for example
npm run generate, and Output directory, for example.output/publicfor Nuxt or_sitefor Eleventy. - Click Save & deploy.
Build tools run with the server's newest Node.js, or the version from .nvmrc, .node-version or engines.node when the runtime is node.
Environment variables in the build
Front-end frameworks embed some variables at build time, for example VITE_API_URL for Vite or NEXT_PUBLIC_* for Next.js. Add them in Environment variables and click Save & redeploy: they are available to the build command. Everything embedded in the output is public, so never put secrets there.
How files are served
- The website's document root points to
current/<root directory>/<output directory>, wherecurrentis the live release. - Directory requests serve
index.html. A missing file returns 404. - Images, CSS, JavaScript, fonts and videos (
css,js,mjs,jpg,jpeg,png,gif,webp,avif,svg,ico,woff,woff2,ttf,eot,mp4,webm) are sent with a 30-day browser cache (Cache-Control: public). HTML has no such header, so browsers check for a new version. - After each build, ZoPanel stores compressed copies (
.gz, and.brwhen the server has Brotli) of text files between 1 KB and 5 MB: CSS, JavaScript, SVG, JSON, HTML, XML, TXT and WebAssembly. nginx sends them without compressing on every request.
Tip: because assets are cached for 30 days, use file names that change with their content (Vite, Create React App, Angular and Next.js do this by default). An unchanged name such as style.css may stay cached in visitors' browsers after a deploy.
Single-page apps (client-side routing)
ZoPanel has no automatic SPA fallback: a direct visit to /dashboard/settings returns 404 if that file does not exist. Choose one of these:
- Hash routing (
/#/dashboard), which needs nothing on the server. - Prerendering each route to its own
index.html(Astro, Next.js export, Gatsby, Docusaurus and VitePress do this). - Custom error page: on the website's Tools tab, set the 404 page in Custom error pages to
/index.html. The app loads on every route, but the response status stays 404, which search engines treat as a missing page. - Administrators can return the app with status 200 by adding this line on the website's Advanced tab under Custom nginx directives, then Test & save:
error_page 404 =200 /index.html;
Leave the 404 Custom error page empty when you use this directive.
Updates and rollback
Each deploy builds a new release in domains/<domain>/releases/<timestamp>/, then switches current to it in one step, so visitors never see a half-uploaded site. If the build fails, the live release is untouched.
The Releases card keeps the last 5 builds; Rollback switches back to one instantly, without rebuilding. To deploy automatically on every push, see Git deploy.
Note: files you upload into a release folder disappear with the next deploy. Put user uploads in a Persistent path, or in S3 storage.
Troubleshooting
| Message or symptom | Cause and fix |
|---|---|
| 404 on every page after the deploy | Output directory is wrong: the build wrote elsewhere. Check the folder in the build log in Tasks and correct it with Auto-detect off. |
| 404 when reloading a page of a React/Vue app | Client-side routing: see Single-page apps. |
build failed: … |
Read the build log. With npm ci, the lockfile must match package.json. |
a start command is required for server apps |
The project was detected as a Node.js server (for example a Vite project with a start script). Turn Auto-detect off and set Type to Static site. |
Node.js is not installed — install it under Runtimes |
The build needs Node.js. An administrator installs it under Runtimes. |
| Old CSS or JavaScript after a deploy | The browser cache (30 days) keeps files whose name did not change. Use hashed file names or add a version to the URL. |
403 on a .php file |
Static websites never run PHP. Use a PHP website instead. |
| Placeholder page on an HTML website | Upload into public_html, with an index.html at its top level. |
Related
- Your first website
- Git deploy
- Node.js apps
- Website tools
- Vite: deploying a static site
- Next.js static exports