Static Sites (SSG Preview)
Build and deploy a static version of your frontend using the Webstir frontend provider. This preview relies on webstir-frontend directly and is intended for marketing/landing sites and simple apps.
See also: CSS Playbook for the minimal, convention-first styling approach used by the SSG starter.
When To Use
- You want a fast, cacheable site (e.g., company landing page) with no server-side rendering or dynamic HTML.
- You are comfortable using the
webstir-frontendCLI alongsidewebstirwhile SSG support is integrated into the main orchestrator.
Requirements
- A standard Webstir workspace layout with frontend assets:
src/frontend/app/app.htmlsrc/frontend/pages/<page>/index.html|css|ts
@webstir-io/webstir-frontendavailable (installed via framework packages or as a dev dependency).
Build a Static Frontend
From the workspace root:
webstir publish
What happens:
publishruns the standard frontend/backend pipelines in production mode.- In
webstir.mode: "ssg"workspaces, the frontend defaults to SSG publish; use--frontend-mode bundleto override, or--frontend-mode ssgto opt in from other modes. - The frontend provider creates optimized assets in
dist/frontend/**and, in SSG publish mode, adds static-friendly aliases:dist/frontend/pages/<page>/index.html(page HTML)dist/frontend/<page>/index.html(pretty URL alias)dist/frontend/index.htmlwhenpages/home/index.htmlexists.
- Publish injects small inline critical CSS blocks (app shell + docs layout) to reduce layout shifts; full stylesheets still load normally.
Content Root (Markdown)
If you are using the Markdown content pipeline, the frontend provider needs to know where your .md files live:
-
By default, it looks at
src/frontend/content/**. -
You can override this with
src/frontend/frontend.config.json:{
"paths": {
// relative to your workspace root
"contentRoot": "docs"
}
}
With this override, Markdown under <workspaceRoot>/docs/** is converted into /docs/... routes during SSG publish.
Static Paths from Module Metadata
You can describe SSG views in package.json under webstir.moduleManifest.views. SSG publish uses these hints to create additional index.html aliases and (when a backend view loader exists) generate per-page view-data.json.
Note: SSG routing is driven by webstir.moduleManifest.views (pages). Route entries under webstir.moduleManifest.routes are for backend APIs; if you add renderMode, staticPaths, or ssg metadata to routes, the frontend SSG publish step will fail and ask you to move that metadata to a view.
Example:
{
"webstir": {
"moduleManifest": {
"views": [
{
"name": "HomeView",
"path": "/",
"staticPaths": ["/"]
},
{
"name": "AboutView",
"path": "/about",
"staticPaths": ["/about", "/about/team"]
}
]
}
}
}
Clean default (simple pages):
{
"webstir": {
"mode": "ssg",
"moduleManifest": {
"views": [
{ "name": "HomeView", "path": "/" },
{ "name": "AboutView", "path": "/about" }
]
}
}
}
Behavior today:
- Each
staticPathsentry is treated as a URL path to alias. - The first path segment is mapped to a page folder (for example,
/aboutand/about/teammap tosrc/frontend/pages/about). - Root (
"/") maps to thehomepage when present.
In webstir.mode: "ssg" workspaces, views default to renderMode: "ssg" when omitted; set renderMode explicitly when you want to override (for example, spa).
For simple (non-parameterized) views, staticPaths is optional. When omitted in an ssg workspace, it defaults to [path]. Use staticPaths when you want extra aliases (like /about/team) or when a view has params (like /blog/:slug).
To scaffold a page and its SSG metadata in one step:
npx webstir-frontend add-page about --workspace "$PWD"
This:
- Creates
src/frontend/pages/about/index.html|css|ts. - In
webstir.mode: "ssg"workspaces, no manifest entry is needed (paths are inferred fromsrc/frontend/pages/**). - Outside
ssgmode, it ensureswebstir.moduleManifest.viewshas an entry withpath: "/about",renderMode: "ssg", andstaticPaths: ["/about"].
GitHub Pages
GitHub Pages can serve the static dist/frontend output from a branch like gh-pages.
Basic flow:
webstir publish --frontend-mode ssg
mkdir -p .gh-pages && rm -rf .gh-pages/*
cp -R dist/frontend/* .gh-pages/
Then:
- Commit
.gh-pages(or use a CI job to populate it). - Push it to a branch configured for GitHub Pages (e.g.,
gh-pages).
Notes:
- Set the Pages root to the repository root when the branch only contains static assets.
- If you host the site under a subpath, adjust asset paths or configure your reverse proxy accordingly.
CI example (GitHub Actions, sketch only):
name: Static Site (SSG)
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Build static site
run: dotnet run --project webstir-dotnet/CLI/CLI.csproj -- publish --frontend-mode ssg
- name: Deploy to GitHub Pages
run: |
mkdir -p out
cp -R dist/frontend/* out/
# Replace this step with your preferred Pages deploy action
S3 + CloudFront
You can upload the static assets to an S3 bucket and serve them via CloudFront.
High-level steps:
-
Build the static frontend:
webstir publish --frontend-mode ssg -
Sync
dist/frontend/**to your bucket (example using AWS CLI):aws s3 sync dist/frontend "s3://your-bucket-name" --delete -
Configure CloudFront:
- Origin: the S3 bucket (static website hosting or origin access as appropriate).
- Default root object:
index.html. - Optional: redirects or error responses for
/and other routes depending on your URL structure.
Notes:
index.htmlaliases created by--frontend-mode ssgallowhttps://example.com/,https://example.com/about/, etc., to resolve without custom routing rules.- Set appropriate cache headers at upload or via CloudFront behaviors (e.g., long-lived caching for hashed assets, shorter for HTML).
Current Status
- SSG support via
webstir publish --frontend-mode ssgis an early preview and focuses on generating static HTML, per-page view data, and friendly URLs.