@krantho/static
Build a krantho site from its markdown to static files, with each page's structured data, the sitemap and llms.txt.
The static mode of krantho. A site reads its markdown files at build time and writes every page, its structured data and its SEO files once. It has no database, no API and no Studio. Deploy to GitHub Pages shows a whole site.
Install#
pnpm add @krantho/static @krantho/config @krantho/schema @krantho/ui-react @krantho/seo zodpnpm create krantho with Static site (Next.js, GitHub Pages) sets it up for you.
createStaticSite#
import { createStaticSite } from '@krantho/static'
import { initServerLoader } from '@krantho/ui-react/server'
import config from '../krantho.config'
import { collections } from '../krantho.collections'
export const site = createStaticSite({ config, collections })
initServerLoader(site.loader, site.rendering)It takes the configuration and collections the server takes, and returns:
| Member | What it is |
|---|---|
loader | The content loader pages read through: the live version of each document, and each page's JSON-LD composed from the seo section of the configuration. Pass it to initServerLoader() |
params(collection, locale?) | One { slug } for each published document of the collection, for generateStaticParams |
sitemapXml() | sitemap.xml: every published, indexable page, dated by its file's last commit (see Requirements) |
llmsTxt() | llms.txt: the site's name and summary, then each published, indexable page under its collection's heading, with a link and its description |
robotsTxt() | robots.txt, allowing every crawler and naming the sitemap |
basePath | The path of site.baseUrl (/docs-site), or '' at a host's root |
rendering | How pages render their markdown: basePath, resolveUrl (see Links between files) and atNotation (content.atNotation). Pass it to initServerLoader() |
config | The resolved configuration |
Serve each file from a route written once at build:
import { site } from '../../lib/krantho'
export const dynamic = 'force-static'
export async function GET(): Promise<Response> {
return new Response(await site.sitemapXml(), {
headers: { 'content-type': 'application/xml; charset=utf-8' },
})
}Requirements#
createStaticSite() throws a KranthoConfigError naming each problem when:
persistence.provideris notfilesystem: the markdown files are the content;cache.pagesis notnone: every page is rendered once, at build;- a collection with pages has no
path, or one that does not end in/: each page is written as a folder, and the sitemap, llms.txt and canonical URLs must name the address it is served at. Writepath: '/docs/:slug/'.
Pages are dated by their files' last git commit. Without the repository's full history, such as a shallow CI checkout, the build warns once and dates them by modification time: check out with fetch-depth: 0.
Links between files#
site.rendering.resolveUrl lets a folder of markdown read the same on GitHub and on the site. A URL written relative to a document's file is rewritten when the page renders:
Written in docs/setup.md | Rendered (site at /docs-site) |
|---|---|
[Apps](apps.md#limits) | /docs-site/apps/#limits |
[Home](README.md), with indexFile: 'README.md' | /docs-site/ |
 | /docs-site/images/flow.png |
The same holds for the src and href of raw <img> and <a> tags, such as <img src="images/flow.png" width="340">. A link to a markdown file goes to that document's page, in whichever collection reads the file. Any other file inside the collection's directory is served from the site's root, so copy it into public/ at the same path (public/images/flow.png). Absolute URLs, URLs that start with / or #, and files outside the collection's directory are left as written. createLinkResolver(collections) builds the same resolver for a site of your own.
basePathOf#
import { basePathOf } from '@krantho/static/base-path'
basePathOf('https://owner.github.io/docs-site') // '/docs-site'
basePathOf('https://docs.example.com') // ''The path a site is served under, from its address. next.config.ts takes it as basePath; it is imported from @krantho/static/base-path, which loads nothing else, so the configuration does not load the server.
Next.js settings#
import { resolveConfig } from '@krantho/config'
import { basePathOf } from '@krantho/static/base-path'
import type { NextConfig } from 'next'
import kranthoConfig from './krantho.config'
const nextConfig: NextConfig = {
output: 'export',
basePath: basePathOf(resolveConfig(kranthoConfig, process.env).site.baseUrl),
trailingSlash: true,
images: { unoptimized: true },
serverExternalPackages: ['@krantho/static', '@krantho/server'],
}
export default nextConfigserverExternalPackages keeps the server out of the bundle: it loads its optional packages (other databases, media stores) only when a configuration asks for them, and a bundler would look for them all.