Skip to guide
Help center

How to deploy a single-page application from GitHub Actions

Build a Vite, React or Vue application in your CI, send its files to the deploy hook of a static site and choose how a missing path answers.

Before you start

You need a ready cluster and a project on it: see projects and clusters. Your site must build to a directory with an index.html at its root, such as the dist directory of a Vite application with React or Vue.

Tenkai builds nothing. Your CI builds the site, and each deployment sends its files to the deploy hook of the static site. The cluster does not need Git builds.

Create the static site

  1. In the project, choose Add resource, then Static site.
  2. Name it, such as site.
  3. Choose its Not found behavior: Single-page application when a client router, such as React Router or Vue Router, handles the paths, or Static site when the build writes a page for each path. See how a missing path answers.
  4. Choose Add static site.
Add a static site dialog with the name site and the Single-page application behavior chosen.Add a static site dialog with the name site and the Single-page application behavior chosen.

Tenkai serves the files with its own server, so a static site takes no entrypoint, no port and no pre-deploy commands. Its variables do not reach the files: set what the build reads, such as VITE_API_URL, in your CI.

Enable the deploy hook

  1. Open the static site, then its Configuration tab.
  2. Under Deploy hook, choose Enable deploy hook.
  3. Add the URL and the secret to the secrets of your repository, as TENKAI_DEPLOY_HOOK_URL and TENKAI_DEPLOY_HOOK_SECRET. Tenkai shows the secret only once.
Deploy hook enabled dialog of a static site, with its URL, its secret hidden behind dots, and the GitHub Actions step with path: dist.Deploy hook enabled dialog of a static site, with its URL, its secret hidden behind dots, and the GitHub Actions step with path: dist.
The step sends the files of dist with each deployment.

Deploy from GitHub Actions

Create .github/workflows/deploy.yml:

name: Deploy

on:
  push:
    branches: [main]

concurrency: deploy

jobs:
  deploy:
    runs-on: ubuntu-24.04
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v7

      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm

      - run: npm ci

      - run: npm run build

      - uses: TenkaiCloud/deploy-action@v1
        with:
          deploy_hook_url: ${{ secrets.TENKAI_DEPLOY_HOOK_URL }}
          deploy_hook_secret: ${{ secrets.TENKAI_DEPLOY_HOOK_SECRET }}
          path: dist

Set path to the output directory of your build. The action sends its content, waits for the deployment to end, and fails the job when Tenkai refuses the files or the deployment fails. concurrency keeps two runs from deploying at once, which Tenkai refuses.

Push the workflow. Once the deployment succeeds, the site answers on its default route.

Deploy from another CI

Pack the content of the output directory, not the directory itself, then send the archive as the body of the request, as application/gzip:

tar -czf site.tar.gz -C dist .
curl --fail-with-body -X POST "$TENKAI_DEPLOY_HOOK_URL" \
  -H "Authorization: Bearer $TENKAI_DEPLOY_HOOK_SECRET" \
  -H "Content-Type: application/gzip" \
  --data-binary @site.tar.gz

Then wait for the deployment to end. The deploy hook guide lists the limits of the archive and what Tenkai refuses.

How a missing path answers

  • Single-page application. A path whose last part has an extension, such as /assets/app-3f2a.js, is a file: Tenkai serves it, or answers 404. Any other path, such as /settings/billing, loads index.html for your router. A route whose last part holds a dot, such as /users/john.doe, answers 404.
  • Static site. A directory serves its index.html, and /docs redirects to /docs/. A missing path answers the 404.html at the root of your files, with a 404 status.

To change it, choose Change… next to Not found behavior in the Configuration tab, then Save and deploy in the menu next to Save. Tenkai deploys the files of the active release with the new behavior: your pipeline sends nothing.

Change the not found behavior dialog with static site chosen, and the Save button with its menu.Change the not found behavior dialog with static site chosen, and the Save button with its menu.
Save and deploy shows once the site has an active release.

Caching

HTML is served with Cache-Control: no-cache, so visitors get the new index.html right after a deployment. Files under /assets/, where Vite writes them with a hash in their name, are cached for a year. A build that writes its hashed files to another directory gets no-cache for them.

Deploy again or roll back

Deploy, on the static site, runs the files of the active release again with the current settings. It cannot bring new files: only your pipeline does. Before the first deployment, it is unavailable.

To undo a bad build, choose Rollback to an earlier release in Deployments, on the overview tab. Tenkai serves its files again, without your pipeline.