# Website tĩnh và build front-end

> Đăng HTML thuần hoặc build website React, Vue, Vite, Angular, Astro, Gatsby, Docusaurus từ Git, với asset nén sẵn, cache, rollback và định tuyến SPA.

Source: https://zopanel.net/vi/docs/static-sites  
Updated: 2026-10-09

Website tĩnh là một thư mục gồm HTML, CSS, JavaScript và file media do nginx phục vụ trực tiếp, không có tiến trình ứng dụng. Dùng cho trang HTML thuần và các framework front-end biên dịch ra file (React, Vue, Svelte, Angular, Astro hoặc Next.js xuất tĩnh, các công cụ tạo tài liệu). Nếu dự án cần server khi chạy, xem [Ứng dụng Node.js](/vi/docs/nodejs).

## Chọn cách đăng website

| Cách | Khi nào dùng | Hoạt động thế nào |
| --- | --- | --- |
| Website **HTML** | File viết tay hoặc đã build sẵn, không cần bước build | Tải file lên `domains/<tên-miền>/public_html`. |
| **Git deploy** | Có repository, có hoặc không có bước build | ZoPanel clone, build và đăng từng bản release, có rollback. |
| Tab **Deploy** với **File đã tải lên (Quản lý file)** | Có mã nguồn nhưng không dùng Git | Tải dự án lên `domains/<tên-miền>/source` rồi deploy. Dự án được build như một bản release Git. |

## Đăng file HTML thuần

1. Vào **Website → Thêm website**, nhập **Tên miền** và chọn **HTML**.
2. Giữ **SSL miễn phí (Let's Encrypt)** đang bật rồi bấm **Tạo website**.
3. Tải file lên `domains/<tên-miền>/public_html` bằng [Quản lý file](/vi/docs/file-manager) hoặc [SFTP](/vi/docs/ftp-sftp). Trang chủ phải là `index.html` (hoặc `index.htm`).

Thay đổi có hiệu lực ngay khi file được lưu. Website loại này không chạy PHP: request tới file `.php` bị từ chối với mã 403.

## Deploy bản build front-end từ Git

1. Vào **Website → Thêm website**, chọn **Git deploy**, nhập **Địa chỉ repository**, **Nhánh** và, với monorepo, **Thư mục gốc**.
2. Bấm **Tạo website**. ZoPanel cài thư viện, chạy build và phục vụ thư mục kết quả.
3. Mở tab **Deploy** để xem cấu hình được nhận diện trong **Build & chạy**: **Loại** là **Website tĩnh** và **Thư mục kết quả build** là thư mục được đăng.

Bạn cũng có thể nối một website **HTML** có sẵn với Git từ tab **Deploy** của nó. Sau lần deploy đầu, website phục vụ thư mục kết quả build thay cho `public_html`.

### Những gì được nhận diện

Khi **Tự nhận diện** đang bật, ZoPanel đọc `package.json` và chọn thư mục kết quả:

| Thư viện trong package.json | Nhận diện là | Thư mục kết quả build |
| --- | --- | --- |
| `vite` (và không có script `start`) | Vite (React/Vue/Svelte) | `dist` |
| `react-scripts` | Create React App | `build` |
| `@angular/core` | Angular | đọc từ `angular.json` (`outputPath` + `/browser`), nếu không có thì `dist` |
| `gatsby` | Gatsby | `public` |
| `@docusaurus/core` | Docusaurus | `build` |
| `vitepress` | VitePress | `.vitepress/dist` |
| `astro` không có `@astrojs/node` | Astro | `dist` |
| `@sveltejs/kit` kèm `@sveltejs/adapter-static` | SvelteKit | `build` |
| `next` với `output: 'export'` trong `next.config.js`, `.mjs` hoặc `.ts` | Next.js | `out` |

Lệnh build là `npm run build` (hoặc `pnpm`/`yarn run build` khi có lockfile tương ứng), lệnh cài đặt theo lockfile, giống [Ứng dụng Node.js](/vi/docs/nodejs). Nếu `package.json` không có script `build`, ghi chú "No build script found in package.json" sẽ hiện ra.

Nếu không có `package.json` (và không có file dự án PHP, Python, Ruby, Go, Java hay .NET), repository có `index.html` ở thư mục gốc được phục vụ dưới dạng **Static HTML** từ thư mục gốc. Trường hợp còn lại được phục vụ như file tĩnh kèm ghi chú "No known framework detected; serving files as a static site."

### Công cụ tạo website khác

Hugo, Jekyll và các công cụ không phải package npm không được cài trên máy chủ. Hãy build trong CI (hoặc trên máy bạn) rồi push thư mục kết quả, ví dụ lên nhánh `deploy`, sau đó deploy nhánh đó. Với các công cụ dựa trên npm không có trong bảng (Eleventy, Nuxt dùng `nuxi generate`…), hãy tự điền cấu hình:

1. Trong tab **Deploy**, tắt **Tự nhận diện** ở **Build & chạy**.
2. Đặt **Loại** là **Website tĩnh**.
3. Điền **Lệnh build**, ví dụ `npm run generate`, và **Thư mục kết quả build**, ví dụ `.output/public` cho Nuxt hoặc `_site` cho Eleventy.
4. Bấm **Lưu & deploy**.

Công cụ build chạy bằng Node.js mới nhất trên máy chủ, hoặc phiên bản lấy từ `.nvmrc`, `.node-version`, `engines.node` khi runtime là `node`.

### Biến môi trường khi build

Framework front-end nhúng một số biến vào bản build, ví dụ `VITE_API_URL` với Vite hay `NEXT_PUBLIC_*` với Next.js. Thêm chúng vào **Biến môi trường** rồi bấm **Lưu & deploy lại**: lệnh build sẽ dùng được. Mọi thứ nhúng vào bản build đều công khai, đừng bao giờ đặt secret ở đó.

## Cách file được phục vụ

- Thư mục gốc web của website trỏ tới `current/<thư mục gốc>/<thư mục kết quả build>`, trong đó `current` là bản release đang chạy.
- Request tới thư mục trả về `index.html`. File không tồn tại trả về 404.
- Ảnh, CSS, JavaScript, font và video (`css`, `js`, `mjs`, `jpg`, `jpeg`, `png`, `gif`, `webp`, `avif`, `svg`, `ico`, `woff`, `woff2`, `ttf`, `eot`, `mp4`, `webm`) được gửi kèm cache trình duyệt 30 ngày (`Cache-Control: public`). HTML không có header này nên trình duyệt sẽ kiểm tra bản mới.
- Sau mỗi lần build, ZoPanel lưu sẵn bản nén (`.gz`, và `.br` khi máy chủ có Brotli) của các file văn bản từ 1 KB đến 5 MB: CSS, JavaScript, SVG, JSON, HTML, XML, TXT và WebAssembly. nginx gửi luôn bản nén mà không phải nén lại ở mỗi request.

**Mẹo:** vì asset được cache 30 ngày, hãy dùng tên file thay đổi theo nội dung (Vite, Create React App, Angular và Next.js mặc định đã làm vậy). File giữ nguyên tên như `style.css` có thể vẫn nằm trong cache trình duyệt của khách sau khi deploy.

## Ứng dụng một trang (định tuyến phía client)

ZoPanel không tự chuyển mọi đường dẫn về `index.html`: truy cập trực tiếp `/dashboard/settings` sẽ trả về 404 nếu không có file đó. Chọn một trong các cách sau:

- **Định tuyến bằng hash** (`/#/dashboard`), không cần cấu hình máy chủ.
- **Prerender** mỗi route thành một `index.html` riêng (Astro, Next.js xuất tĩnh, Gatsby, Docusaurus và VitePress làm sẵn việc này).
- **Trang lỗi riêng:** trong tab **Công cụ** của website, đặt trang **404** ở **Trang lỗi riêng** là `/index.html`. Ứng dụng sẽ tải ở mọi route nhưng mã trả về vẫn là 404, công cụ tìm kiếm coi là trang không tồn tại.
- **Quản trị viên** có thể trả ứng dụng với mã 200 bằng cách thêm dòng sau ở tab **Nâng cao** của website, mục **Cấu hình nginx tuỳ chỉnh**, rồi bấm **Kiểm tra & lưu**:

```nginx
error_page 404 =200 /index.html;
```

Khi dùng dòng cấu hình này, hãy để trống trang 404 trong **Trang lỗi riêng**.

## Cập nhật và rollback

Mỗi lần deploy build một bản release mới trong `domains/<tên-miền>/releases/<thời-điểm>/`, rồi chuyển `current` sang bản đó trong một bước, nên khách không bao giờ thấy website đang tải lên dở dang. Nếu build lỗi, bản đang chạy không bị ảnh hưởng.

Thẻ **Các bản release** giữ 5 bản build gần nhất; **Rollback** chuyển về một bản ngay lập tức, không build lại. Để tự deploy mỗi khi push, xem [Git deploy](/vi/docs/git-deploy).

**Lưu ý:** file bạn tải vào thư mục release sẽ mất ở lần deploy sau. Hãy đặt file người dùng tải lên trong **Đường dẫn lưu trữ cố định**, hoặc dùng [Lưu trữ S3](/vi/docs/apps).

## Xử lý sự cố

| Thông báo hoặc hiện tượng | Nguyên nhân và cách xử lý |
| --- | --- |
| Mọi trang đều 404 sau khi deploy | **Thư mục kết quả build** sai: bản build ghi ra chỗ khác. Xem thư mục trong log build ở **Tác vụ** rồi sửa lại khi đã tắt **Tự nhận diện**. |
| 404 khi tải lại một trang của ứng dụng React/Vue | Định tuyến phía client: xem mục Ứng dụng một trang ở trên. |
| `build failed: …` | Đọc log build. Với `npm ci`, lockfile phải khớp `package.json`. |
| `a start command is required for server apps` | Dự án bị nhận diện là server Node.js (ví dụ dự án Vite có script `start`). Tắt **Tự nhận diện** và đặt **Loại** là **Website tĩnh**. |
| `Node.js is not installed — install it under Runtimes` | Bước build cần Node.js. Quản trị viên cài ở **Runtime**. |
| CSS hoặc JavaScript cũ sau khi deploy | Cache trình duyệt (30 ngày) giữ các file không đổi tên. Dùng tên file có mã hash hoặc thêm phiên bản vào URL. |
| Lỗi 403 với file `.php` | Website tĩnh không bao giờ chạy PHP. Hãy dùng website PHP. |
| Website **HTML** vẫn hiện trang mặc định | Tải file vào `public_html`, có `index.html` ở cấp ngoài cùng. |

## Xem thêm

- [Website đầu tiên](/vi/docs/first-website)
- [Git deploy](/vi/docs/git-deploy)
- [Ứng dụng Node.js](/vi/docs/nodejs)
- [Công cụ website](/vi/docs/website-tools)
- [Vite: deploy website tĩnh](https://vite.dev/guide/static-deploy)
- [Next.js: xuất tĩnh](https://nextjs.org/docs/app/guides/static-exports)
