A link-in-bio site generator: describe yourself in a single
config.jsonand get a fast, accessible, private, static page you can host anywhere (GitHub Pages, Netlify, Vercel, Cloudflare Pages, Nginx…). Built on Deno, Preact + Signals, and BeerCSS (Material Design 3).
- One file, whole page: everything — profile (identity + head metadata), the ordered
blocksflow (links as rows/buttons/cards, Markdown sections optionally framed as cards, figures optionally framed as cards, and interactive islands), theme, footer — lives in a versionedconfig.json, validated with ArkType. - Static head + client-rendered body: the build bakes the per-profile SEO
<head>(title / OG / Twitter / JSON-LD) and the PWAmanifest.jsoninto a realindex.html, whilelinklist.jsfetches./config.jsonat runtime, applies the theme and renders the Preact app. Crawlers and link previews see the metadata without JS. Paths are relative, so it works at a domain root or a subfolder. - 100% Pure Deno: no Node runtime and no
node_modulesworkflow. The static build is declarative via@vanaware/buildit(scripts/esbuild.jsonc/scripts/denobuild.jsonc); a small Cliffy CLI in core (packages/core/src/cli.ts) generates the site files and validates configs. - Pure BeerCSS + native theming: the whole page is BeerCSS markup. Brand skins
(
violet,indigo) selectable viaconfig.theme.presetoverride only BeerCSS color tokens; dark/light come from BeerCSS'sbody.dark/body.light(mode: autoresolves without a flash). - Rich text where you want it: Markdown (CommonMark + GFM) in the bio, tagline,
section bodies, link subtitles and footer — rendered to real Preact VNodes, no
dangerouslySetInnerHTML. - Interactive islands: an
islandblock hydrates a plain-JS Preact component frompublic/islands/in place, reusing the app's single shared Preact/Signals instance — no build step, no second copy, and the page still works (static placeholder) if an island never loads. - Private by default: no analytics or click tracking — there is none to opt into.
- Deno 2.x (pinned in
.tool-versions).
Start from a template: docs/templates/minimal.config.json (bare-bones) or
packages/core/public/config.json (the fully-annotated sample that powers the official
site). The shape mirrors packages/core/src/lib/types.ts, with the JSON Schema at
packages/core/schema/config.schema.json (published at
https://vanaware.github.io/linklist/config.schema.json).
git clone https://github.com/vanaware/linklist.git
cd linklist
# Copy a starter and edit it
cp packages/core/public/config.json site/config.json
# Validate your config at any time
deno task validate-config site/config.jsondeno task dev # build, then serve packages/server/build/distOpen the URL the server prints. (See Building for the sandbox fallback.)
The build is entirely driven by buildit config files — first deno task linklist:build
regenerates index.html / manifest.json / robots.txt (SEO head baked in) into
packages/core/public, then the bundler cleans the output dir, copies the static assets
- theme CSS +
config.json, and bundlescore/src/entry.ts→linklist.js:
| Task | Engine | Output | Notes |
|---|---|---|---|
deno task esbuild |
esbuild (preferred) | packages/server/build/dist |
Dev bundle; runs linklist:build first. |
deno task denobuild |
Deno in-process bundler | packages/server/build/dist |
Fallback for sandboxes where esbuild's subprocess can't spawn. |
deno task linklist:build |
core CLI (src/cli.ts) |
packages/core/public |
Site-file codegen only (chained by the tasks above). |
⚠️ Local sandbox note: on some locked-down containers esbuild's npm subprocess cannot start (ChildProcess.unrefundefined). There, usedeno task denobuild(Deno's own in-process bundler) to producelinklist.js, thendeno task dev. CI runners build with esbuild normally.
Everything above is wrapped by one command, which builds and then serves the output with the static server:
deno task dev # denobuild + packages/server file-server (port from packages/server/.env)A skin is a small CSS file in packages/core/themes that overrides only the BeerCSS
color tokens (--primary, --on-primary, --primary-container, --on-primary-container,
--font); linklist-base.css is always loaded for the shared layout. Add a skin by
dropping a <name>.css in packages/core/themes — it is copied into the build
automatically. A custom primary_color automatically derives a readable --on-primary
ink; surfaces and elevation stay owned by BeerCSS per light/dark mode. The whole
--ll-* token system was removed. See docs/theming.md.
├── packages/
│ ├── core/ # @vanaware/linklist (the engine)
│ │ ├── src/
│ │ │ ├── mod.ts # public API surface (side-effect-free)
│ │ │ ├── entry.ts # browser bootstrap -> bundled linklist.js
│ │ │ ├── cli.ts # build + validate CLI (Cliffy; the file-writing edge)
│ │ │ ├── version.ts # build-stamped APP_VERSION (generated)
│ │ │ ├── lib/ # pure engine: types, schema, theme, seo, generate
│ │ │ └── components/ # Preact UI + signals + markdown renderer
│ │ ├── schema/ # config.schema.json (published for editors + the site)
│ │ ├── themes/ # linklist-base.css + BeerCSS brand skins (violet, indigo)
│ │ └── public/ # index.html shell, config.json, manifest, favicon, robots, islands/
│ └── server/ # static file-server (serveDir) used for local preview
├── scripts/ # buildit configs + export/version tooling (I/O edge)
├── .github/workflows/ # ci.yml (gate) + pages.yml (build & deploy)
└── deno.jsonc # workspace + tasks
deno task tests # check:all + lint + fmt:check + test (the full gate)
deno task test # BDD tests (@std/testing/bdd + @std/assert) only- GitHub Pages: push a
v*.*tag (or run the workflow manually).pages.ymlruns the gate, builds the site intopackages/server/build/dist, and publishes it. The site uses relative paths, so a project-page subfolder works out of the box. - Any static host: run
deno task esbuild(ordenobuild) and uploadpackages/server/build/dist— it is fully self-contained. - Custom domain: point DNS at your host and add a
CNAME; never hardcode base paths.
Full guides live in docs/:
- Getting started
- Configuration reference
- Theming
- Islands
- Deploy
- API reference
- Site templates — starter
config.jsonfiles
Contributing? Read the architectural rules in AGENTS.md. For current status and the next milestone, see CURRENT.md.
MIT © Vanaware
{ "theme": { "preset": "violet", // a brand skin CSS file from packages/core/themes "mode": "auto", // light | dark | auto "custom": { "primary_color": "#6750a4", "font_family": "Inter" } // BeerCSS token overrides } }