Skip to content

Deploy Your VitePress Site

The following guides are based on some shared assumptions:

  • The VitePress site is inside the docs directory of your project.

  • You are using the default build output directory (.vitepress/dist).

  • VitePress is installed as a local dependency in your project, and you have set up the following scripts in your package.json:

    package.json
    json
    {
      "scripts": {
        "docs:build": "vitepress build docs",
        "docs:preview": "vitepress preview docs"
      }
    }

Build and Test Locally

  1. Run this command to build the docs:

    sh
    $ npm run docs:build
  2. Once built, preview it locally by running:

    sh
    $ npm run docs:preview

    The preview command will boot up a local static web server that will serve the output directory .vitepress/dist at http://localhost:4173. You can use this to make sure everything looks good before pushing to production.

  3. You can configure the port of the server by passing --port as an argument.

    json
    {
      "scripts": {
        "docs:preview": "vitepress preview docs --port 8080"
      }
    }

    Now the docs:preview method will launch the server at http://localhost:8080.

Setting a Public Base Path

By default, we assume the site is going to be deployed at the root path of a domain (/). If your site is going to be served at a sub-path, e.g. https://mywebsite.com/blog/, then you need to set the base option to '/blog/' in the VitePress config.

Example: If you're using Github (or GitLab) Pages and deploying to user.github.io/repo/, then set your base to /repo/.

Relocatable Builds (Relative Base)

When the final URL of the site isn't known at build time — an IPFS gateway (https://gateway/ipfs/<cid>/…), the Wayback Machine, a shared folder, docs bundled into an app — set base to './':

ts
export default {
  base: './'
}

Every page then references assets and other pages relative to its own location, and the client runtime recovers the real mount point when the page loads. The same build works from any sub path without rebuilding — including several at once — with routing, search and prefetching fully functional.

Opening the generated HTML files straight from the file system (file://) also works as a styled, fully navigable static site. Browsers block JavaScript modules over file://, so there is no hydration there — interactive features like search stay inactive, while all pre-rendered content and links keep working.

A few things to know:

  • Keep cleanUrls off (the default): portable output needs links that end in .html, since there is no server to rewrite pretty URLs.
  • 404.html is generated for the root depth. Hosts that serve it as a fallback for arbitrarily deep URLs will render it without styles (there is no correct relative prefix for an unknown depth).
  • head entries are emitted verbatim, as always — avoid root-absolute paths like /favicon.ico there and prefer absolute URLs or transformHead.
  • Raw HTML <a> tags in Markdown keep their href as written — use Markdown link syntax for site-absolute links (embedded <img> sources go through the asset pipeline and are handled).
  • Links created by createContentLoader content stay site-absolute (their HTML is embedded into other pages, so no single relative prefix is correct) — they resolve only for a root mount.
  • Serve pages at their canonical URLs: the root as /dir/ (not /dir), and no added trailing slashes on page URLs. The relative prefix is resolved against the URL the browser actually shows, and virtually all static hosts canonicalize this way already.
  • The dev server always serves at /; the relative behavior applies to the production build.

HTTP Cache Headers

If you have control over the HTTP headers on your production server, you can configure cache-control headers to achieve better performance on repeated visits.

The production build uses hashed file names for static assets (JavaScript, CSS and other imported assets not in public). If you inspect the production preview using your browser devtools' network tab, you will see files like app.4f283b18.js.

This 4f283b18 hash is generated from the content of this file. The same hashed URL is guaranteed to serve the same file content - if the contents change, the URLs change too. This means you can safely use the strongest cache headers for these files. All such files will be placed under assets/ in the output directory, so you can configure the following header for them:

Cache-Control: max-age=31536000,immutable
Example Netlify _headers file
/assets/*
  cache-control: max-age=31536000
  cache-control: immutable

Note: the _headers file should be placed in the public directory - in our case, docs/public/_headers - so that it is copied verbatim to the output directory.

Netlify custom headers documentation

Example Vercel config in vercel.json
json
{
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "max-age=31536000, immutable"
        }
      ]
    }
  ]
}

Note: the vercel.json file should be placed at the root of your repository.

Vercel documentation on headers config

Platform Guides

Netlify / Vercel / Cloudflare Pages / AWS Amplify / Render

Set up a new project and change these settings using your dashboard:

  • Build Command: npm run docs:build
  • Output Directory: docs/.vitepress/dist
  • Node Version: 20 (or above)

WARNING

Don't enable options like Auto Minify for HTML code. It will remove comments from output which have meaning to Vue. You may see hydration mismatch errors if they get removed.

GitHub Pages

  1. Create a file named deploy.yml inside .github/workflows directory of your project with some content like this:

    .github/workflows/deploy.yml
    yaml
    # Sample workflow for building and deploying a VitePress site to GitHub Pages
    #
    name: Deploy VitePress site to Pages
    
    on:
      # Runs on pushes targeting the `main` branch. Change this to `master` if you're
      # using the `master` branch as the default branch.
      push:
        branches: [main]
    
      # Allows you to run this workflow manually from the Actions tab
      workflow_dispatch:
    
    # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
    permissions:
      contents: read
      pages: write
      id-token: write
    
    # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
    # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
    concurrency:
      group: pages
      cancel-in-progress: false
    
    jobs:
      # Build job
      build:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v5
            with:
              fetch-depth: 0 # Not needed if lastUpdated is not enabled
          # - uses: pnpm/action-setup@v4 # Uncomment this block if you're using pnpm
          #   with:
          #     version: 9 # Not needed if you've set "packageManager" in package.json
          # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun
          - name: Setup Node
            uses: actions/setup-node@v6
            with:
              node-version: 24
              cache: npm # or pnpm / yarn
          - name: Cache VitePress
            uses: actions/cache@v4
            with:
              path: docs/.vitepress/cache
              key: ${{ runner.os }}-vitepress-${{ hashFiles('docs/**', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lockb') }}
              restore-keys: |
                ${{ runner.os }}-vitepress-
          - name: Setup Pages
            uses: actions/configure-pages@v4
          - name: Install dependencies
            run: npm ci # or pnpm install / yarn install / bun install
          - name: Build with VitePress
            run: npm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build
          - name: Upload artifact
            uses: actions/upload-pages-artifact@v3
            with:
              path: docs/.vitepress/dist
    
      # Deployment job
      deploy:
        environment:
          name: github-pages
          url: ${{ steps.deployment.outputs.page_url }}
        needs: build
        runs-on: ubuntu-latest
        name: Deploy
        steps:
          - name: Deploy to GitHub Pages
            id: deployment
            uses: actions/deploy-pages@v4

    WARNING

    Make sure the base option in your VitePress is properly configured. See Setting a Public Base Path for more details.

  2. In your repository's settings under "Pages" menu item, select "GitHub Actions" in "Build and deployment > Source".

  3. Push your changes to the main branch and wait for the GitHub Actions workflow to complete. You should see your site deployed to https://<username>.github.io/[repository]/ or https://<custom-domain>/ depending on your settings. Your site will automatically be deployed on every push to the main branch.

GitLab Pages

  1. Set outDir in VitePress config to ../public. Configure base option to '/<repository>/' if you want to deploy to https://<username>.gitlab.io/<repository>/. You don't need base if you're deploying to custom domain, user or group pages, or have "Use unique domain" setting enabled in GitLab.

  2. Create a file named .gitlab-ci.yml in the root of your project with the content below. This will build and deploy your site whenever you make changes to your content:

    .gitlab-ci.yml
    yaml
    image: node:24
    pages:
      cache:
        paths:
          - node_modules/
      script:
        # - apk add git # Uncomment this if you're using small docker images like alpine and have lastUpdated enabled
        - npm install
        - npm run docs:build
      artifacts:
        paths:
          - public
      only:
        - main

Azure

  1. Follow the official documentation.

  2. Set these values in your configuration file (and remove the ones you don't require, like api_location):

    • app_location: /
    • output_location: docs/.vitepress/dist
    • app_build_command: npm run docs:build

CloudRay

You can deploy your VitePress project with CloudRay by following these instructions.

Firebase

  1. Create firebase.json and .firebaserc at the root of your project:

    firebase.json:

    firebase.json
    json
    {
      "hosting": {
        "public": "docs/.vitepress/dist",
        "ignore": []
      }
    }

    .firebaserc:

    .firebaserc
    json
    {
      "projects": {
        "default": "<YOUR_FIREBASE_ID>"
      }
    }
  2. After running npm run docs:build, run this command to deploy:

    sh
    firebase deploy

Heroku

  1. Follow documentation and guide given in heroku-buildpack-static.

  2. Create a file called static.json in the root of your project with the below content:

    static.json
    json
    {
      "root": "docs/.vitepress/dist"
    }

Hostinger

You can deploy your VitePress project with Hostinger by following these instructions. While configuring build settings, choose VitePress as the framework and adjust the root directory to ./docs.

Stormkit

You can deploy your VitePress project to Stormkit by following these instructions.

Surge

After running npm run docs:build, run this command to deploy to Surge:

sh
npx surge docs/.vitepress/dist

harvis

After running npm run docs:build, run this command to deploy to harvis:

sh
npx harvis docs/.vitepress/dist

nginx

Here is a example of an nginx server block configuration. This setup includes gzip compression for common text-based assets, rules for serving your VitePress site's static files with proper caching headers as well as handling cleanUrls: true.

nginx
map $uri $cache_control {
    ~^/assets/  "public, max-age=31536000, immutable";
    default     "no-cache";
}

server {
    listen 8080;
    listen [::]:8080;
    server_name _;

    root /usr/share/nginx/html;
    index index.html;
    charset utf-8;
    server_tokens off;

    absolute_redirect off;

    gzip on;
    gzip_vary on;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_types
        application/javascript
        application/json
        application/manifest+json
        image/svg+xml
        text/css
        text/javascript
        text/plain;

    add_header Cache-Control $cache_control always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    location / {
        try_files $uri $uri.html $uri/index.html =404;
    }

    location ~ ^(?<page>.+)/$ {
        if (-f $document_root$page.html) {
            return 301 $page$is_args$args;
        }
        try_files $page/index.html =404;
    }

    error_page 404 /404.html;
}

Released under the MIT License.