Build a website
gtmesh produces a mesh — a committed graph (projected into site.manifest.json) plus per-page content bundles.
It does not produce a website. Turning the mesh into a site is the job of a
static-site generator (SSG) you own — Next.js, Astro, Eleventy, whatever —
and this section is the guide for that build.
The bridge is the content-types package:
gtmesh types generates a self-contained
TypeScript package your SSG imports. It ships the readers and the
config-derived constants an SSG needs, so most of what a site build wants is a
function call, not something you re-derive.
The shape of an SSG build
Every mesh-backed site does the same five things. Each maps to something the package gives you:
- List what to render — the committed graph (projected into
site.manifest.json) is the index of every page that could exist. Filter it by the current environment’s publish status → the render set.pageSlugs()/loadPagesByType()do this. - Render each page — resolve a slug to its content bundle with
loadPage<TypePage>(slug), typed by the generated<Type>Pageinterface. - Wire navigation — breadcrumbs, hub members, related links, facets. The
package ships these as feed functions:
breadcrumbs(),hubMembers(),relatedLinks(), andentitiesByFacet(), all returning ready-to-render cards. - Emit URLs & redirects — canonical/sitemap/OG URLs from
siteBaseUrl(), asset URLs fromassetUrl(), 301s fromloadRedirects(). - Keep it honest — wire
gtmesh types --checkinto CI so the package can never drift from the schemas and config.
Install & wire it up
Generate the package (default packages/content-types/) and reference it as a
workspace dependency:
gtmesh types # writes the package + a pnpm workspace + a CI drift gate// your SSG app's package.json
{
"dependencies": {
"@your-project/content-types": "workspace:*"
}
}import {
loadPage, loadPagesByType, pageSlugs, // pages
loadGraph, loadManifest, // the graph-native reader + the link tree (getById/byLabel/roleOf/edge-walks)
breadcrumbs, hubMembers, relatedLinks, // navigation feeds (pass the GraphView)
pageSummaries, labelForSlug, // the card index
loadEntities, entitiesByFacet, // entities & facets (deprecated → loadGraph)
assetUrl, copyMeshAssets, // co-located assets
loadRedirects, // 301s
SECTIONS, renderableStatuses, viewForEnv, siteBaseUrl, // config-derived constants
type GuidePage, type GraphNode, // generated + graph types
} from "@your-project/content-types";The package reads the committed mesh from disk. It resolves the mesh root from
CONTENT_ROOT (falling back to process.cwd()), or you pass { root } to any
loader. Point it at wherever your build checks out the mesh.
The full function-by-function reference is on the content-types package page. The two pages below cover the parts that trip people up.
Base URLs, sitemaps & canonical tags
Every canonical, sitemap, and Open Graph URL is built from one origin — the
site’s base URL. Declare it once so the mesh owns it and prose, the writer, and
the SSG can’t disagree. Set it with gtmesh config
rather than hand-editing the YAML — the writer is comment-preserving and
validates before it saves:
gtmesh config set site.base_url https://example.com
gtmesh config set environments.dev.base_url https://staging.example.com # optional per-env overridewhich lands as:
site:
base_url: https://example.com
environments:
prod: { renders: [published] }
dev: { renders: [writing, review, published], base_url: https://staging.example.com }Then regenerate the package (gtmesh types) so the value flows into site.ts,
and read it with siteBaseUrl(env) — the env-specific override if set, else the
committed default:
const origin = siteBaseUrl(process.env.DEPLOY_ENV) ?? "http://localhost:3000";
const canonical = new URL(page.slug, origin).toString();A sitemap is then pageSlugs({ status: renderableStatuses(env) }) mapped
through that origin. A robots.txt or llms.txt is the same slug set.
site.base_url is the committed default. An SSG deploying to an ephemeral
preview URL can still override at the edge with its own runtime env var — the
config value is the source of truth, not a straitjacket.
Related
- Publish status & environments — which pages render where
- Navigation & relationships — hubs, facets, breadcrumbs, and the two membership mechanisms
- Projections (cards & lists) — turn pages into typed, cached list-items via config
- Assets (images & documents) — resolve asset URLs and ship co-located files
- Deploying on Cloudflare Workers — branch-gating the render-env so previews don’t leak as indexable, self-canonical prod
- Content-types package — the generated package + the full library API
- The mesh & links — how the link graph is derived
- Redirects — the old→current ledger the SSG emits 301s from