Static Site Build
The accent build command generates a directory of static HTML, CSS, and assets that can be served by any web server or hosting provider. Static builds are free – no license key is required.
The accent build Command
accent build --output ./public --base-url https://example.com
Flags
| Flag | Description | Default |
|---|---|---|
--output / -o | Output directory for generated files | output |
--base-url | Base URL for absolute links and sitemaps | (none; falls back to site.url) |
--base-path | Explicit sub-path prefix for every internal URL (overrides config and any value derived from --base-url/site.url) | (none; derived or falls back to site.base_path) |
--config / -c | Path to config file | ./config.yaml |
--clean | Remove output directory before building | false |
The --base-url flag is important for production builds. It sets the base for absolute URLs in sitemaps, RSS feeds, and canonical links. Always set it to your production domain. If its path component is non-root – for example https://username.github.io/repo – that path is also used to derive the site’s base path automatically, so every internal link and asset is prefixed to match. See Deploy to GitHub Pages below.
Output Directory Structure
A typical build produces the following:
public/
index.html # Home page
about/
index.html # About page
blog/
index.html # Blog listing (page 1)
page/
2/index.html # Blog listing (page 2)
my-first-post/
index.html # Blog post
docs/
index.html # Docs index
getting-started/
index.html
feed.xml # RSS feed
sitemap.xml # XML sitemap
robots.txt # Robots configuration
assets/ # Theme CSS, JS, and images
css/
style.css
js/
main.js
media/ # Pre-processed media files
blog/
my-first-post/
hero.webp
hero-thumb.webp
Every page becomes an index.html inside a directory matching its URL path. This produces clean URLs without file extensions on any web server.
Trailing slashes on static hosts
Accent’s canonical URL form has no trailing slash (/about, not /about/) — that is what every link, the <link rel="canonical"> tag, the sitemap, and feeds emit, and accent serve/serve-static redirect the slash form to it. Because the build output is directory-per-page (about/index.html), some static hosts apply their own trailing-slash policy on top, and several do the opposite of Accent’s default:
- GitHub Pages, Netlify (default), and Cloudflare Pages redirect
/about→/about/, so a visitor following a canonical link takes one host-side redirect to the slash form. - Vercel (with
cleanUrls: true, as in the config below) serves/aboutdirectly, matching Accent.
This is cosmetic — every link still resolves — but if you want the served form to match Accent’s canonical no-slash form exactly, configure the host to prefer no trailing slash (e.g. Netlify’s Pretty URLs off, or a Cloudflare Pages _redirects rule). Relative .md links between pages are rewritten to absolute clean URLs at build time, so they are unaffected by whichever form the host serves.
You may also see small *.md/index.html files (e.g. blog.md/index.html) next to the real pages. These are redirect stubs that bounce GitHub-style .md links to the clean URL; each is a meta-refresh with a canonical link, and they are excluded from sitemap.xml and feed.xml. To omit them, set content.md_extension_redirects: false in config.yaml.
Preview with serve-static
Before deploying, preview the build output locally using serve-static:
accent build --output ./public --base-url https://example.com accent serve-static --dir ./public # Visit http://127.0.0.1:4403
To preview over HTTPS (useful for testing service workers or mixed-content policies):
accent serve-static --dir ./public --tls # Visit https://127.0.0.1:4403 (accept the self-signed certificate warning)
This is especially useful in CI pipelines for running smoke tests against the built output before deploying.
Deploy to Cloudflare Pages
Cloudflare Pages provides free hosting with a global CDN. Deploy with GitHub Actions:
# .github/workflows/deploy.yml name: Deploy to Cloudflare Pages on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install Accent CMS run: | curl -fsSL https://accentcms.dev/install.sh | sh echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Build site run: accent build --output ./public --base-url ${{ vars.SITE_URL }} - name: Deploy uses: cloudflare/wrangler-action@v3 with: command: pages deploy ./public --project-name=my-site apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Set SITE_URL as a repository variable and CLOUDFLARE_API_TOKEN as a repository secret in your GitHub settings.
Deploy to Netlify
Create a netlify.toml in your project root:
[build]
command = "accent build --output ./public --base-url https://example.com"
publish = "public"
[build.environment]
# Netlify will use this to install Accent CMS
ACCENTCMS_VERSION = "latest"
[[redirects]]
from = "/*"
to = "/404.html"
status = 404
Alternatively, deploy with the Netlify CLI:
accent build --output ./public --base-url https://your-site.netlify.app npx netlify deploy --prod --dir=./public
Deploy to GitHub Pages
GitHub Pages serves a site in one of two ways, and the distinction matters for how you build:
- User or organization sites (
username.github.io) serve at the domain root. Nothing special is needed – treat it like any other domain. - Project sites (
username.github.io/repo) serve under a sub-path. Every page link, theme asset, media path, and search fetch path needs the/repoprefix, or the deployed site 404s on its own assets.
Accent handles project sites automatically: the path component of --base-url sets the site’s base path, so every internal URL Accent emits is prefixed to match. The recipe below works as written – there is no post-build rewriting step required.
Use GitHub Actions to build and deploy to GitHub Pages:
# .github/workflows/pages.yml name: Deploy to GitHub Pages on: push: branches: [main] permissions: contents: read pages: write id-token: write jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install Accent CMS run: | curl -fsSL https://accentcms.dev/install.sh | sh echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Build site run: accent build --output ./public --base-url https://username.github.io/repo - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: ./public deploy: needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4
Enable GitHub Pages in your repository settings under Settings > Pages and select GitHub Actions as the source.
For explicit control – or when --base-url carries no path component but you still want a prefix – pass --base-path directly, or set site.base_path in config.yaml. An explicit value always wins over anything derived from --base-url/site.url:
accent build --output ./public --base-url https://username.github.io/repo --base-path /repo
accent build finishes with a conformance check over the emitted output: if any generated file still contains an unprefixed root-absolute internal URL, the build fails and reports the offending file and line rather than shipping a broken site. During rendering it also warns – with file and line – about theme templates that hardcode a root-absolute href, src, or action instead of using the url() template function; see Theme Portability for the pattern.
Preview the project-site build locally
accent serve-static picks up the resolved site.base_path automatically, so previewing a prefixed build needs no extra flags:
accent build --output ./public --base-url https://username.github.io/repo accent serve-static --dir ./public # Visit http://127.0.0.1:4403/repo
Deploy to Vercel
Create a vercel.json in your project root:
{ "buildCommand": "accent build --output ./public --base-url https://example.vercel.app", "outputDirectory": "public", "cleanUrls": true }
Deploy with the Vercel CLI:
npx vercel --prod
Deploy to Any Web Server
Copy the public/ directory to the document root of any web server:
accent build --output ./public --base-url https://example.com # Copy to a remote server rsync -avz --delete ./public/ user@server:/var/www/example.com/ # Or serve locally with nginx sudo cp -r ./public/* /var/www/html/
nginx Configuration for Static Files
server {
listen 80;
server_name example.com;
root /var/www/example.com;
# Clean URLs: try directory index first
location / {
try_files $uri $uri/index.html =404;
}
# Cache static assets aggressively
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Custom 404 page
error_page 404 /404.html;
}
Caddy Configuration
example.com {
root * /var/www/example.com
file_server
try_files {path} {path}/index.html
}
Differences from Dynamic Serving
The static build output is functionally identical to what the dynamic server renders, with a few differences:
| Aspect | Dynamic Server | Static Build |
|---|---|---|
| Pagination | Rendered on request | Physical page/N/index.html files |
| Plugins | Execute at request time | Execute once at build time |
| Media processing | On-demand (first request triggers processing) | All media pre-processed at build time |
| Content updates | Edit file, refresh browser | Edit file, rebuild, redeploy |
| Config changes | SIGHUP to reload, or restart | Rebuild required |
| RSS/Sitemap | Generated on request | Generated as static files |
Templates, markdown rendering, and page output are identical between the two modes.
CI/CD Pipeline Example
A complete GitHub Actions workflow that builds, tests, and deploys:
# .github/workflows/deploy.yml name: Build and Deploy on: push: branches: [main] pull_request: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install Accent CMS run: | curl -fsSL https://accentcms.dev/install.sh | sh echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Validate content run: accent validate - name: Build site run: accent build --output ./public --base-url ${{ vars.SITE_URL }} - name: Check for broken links run: | npx broken-link-checker-local ./public --recursive - name: Deploy (main branch only) if: github.ref == 'refs/heads/main' && github.event_name == 'push' run: | # Replace with your deployment command rsync -avz --delete ./public/ user@server:/var/www/site/
Pull requests run the build and validation steps without deploying, so you can catch issues before merging.
Next Steps
- Production Server – run Accent CMS as a dynamic service
- Deployment Overview – compare deployment strategies
- Getting Started – initial project setup