@chimichurricode/web-app-kit
v0.1.3
Published
Shared Astro web shell — BaseLayout component + defineWebApp config factory + canonical site scripts. Built on @chimichurricode/design-system + @chimichurricode/analytics.
Readme
@chimichurricode/web-app-kit
Shared Astro web shell for every Chimi web property. Owns the invariant parts of an Astro web (HTML shell, head composition, body class wiring, analytics bootstrap, Astro config defaults) so each consumer only declares the variant parts (nav links, footer columns, copy strings, content collections).
Companion to
@chimichurricode/design-system(which owns tokens, recipes, chrome components) and@chimichurricode/analytics(which owns the analytics adapter). This kit composes them into a turnkey site shell.
What it ships
| Subpath | What you get |
|---|---|
| @chimichurricode/web-app-kit/BaseLayout.astro | The shared <html> + <head> + <body> shell. Renders ThemeBootstrap, SEOHead, SiteHeader, <main> slot, SiteFooter, and the analytics bootstrap from typed props. |
| @chimichurricode/web-app-kit/config | defineWebApp({ siteId, port, preact?, sitemapFilter?, … }) — Astro config factory with the canonical defaults and (when preact: true) the Preact integration + the upstream fix-preact-server-optimize workaround. |
| @chimichurricode/web-app-kit/scripts | defineSiteScripts({ siteId, sitemapFilename?, disallow?, extra? }) — the canonical package.json scripts object. |
| @chimichurricode/web-app-kit/seo | homeAppJsonLd({ siteId, name, description, applicationCategory? }) and homeEditorialJsonLd({ siteId, socials? }) — JSON-LD factories that consolidate the boilerplate previously duplicated across 6 of the 7 webs' src/seo/index.ts. |
| @chimichurricode/web-app-kit/types | Recipe, SEOOverrides, BrandConfig, NavLink, FooterColumn, BaseLayoutProps, DefineWebAppOptions, DefineSiteScriptsOptions — the public type surface. |
Quick start
1. Add the kit to your web
// packages/<your-web>/package.json
{
"dependencies": {
"@chimichurricode/analytics": "*",
"@chimichurricode/design-system": "*",
"@chimichurricode/web-app-kit": "*"
}
}2. Replace your astro.config.mjs
// packages/<your-web>/astro.config.mjs
import { defineWebApp } from "@chimichurricode/web-app-kit/config";
export default defineWebApp({
siteId: "simple-foo-tool",
port: 4346,
preact: true,
// Optional: filter the sitemap (e.g. exclude private routes)
sitemapFilter: (page) => !page.includes("/r/"),
});3. Replace your src/layouts/Base.astro
---
import "../styles/global.css";
import BaseLayout from "@chimichurricode/web-app-kit/BaseLayout.astro";
import type { BaseLayoutProps } from "@chimichurricode/web-app-kit/types";
interface Props extends Pick<BaseLayoutProps, "title" | "description" | "recipe" | "seo"> {}
const { title, description, recipe, seo } = Astro.props;
---
<BaseLayout
siteId="simple-foo-tool"
title={title}
description={description}
recipe={recipe}
seo={seo}
header={{ brand: { label: "Simple Foo Tool", emText: "Foo", byline: "by Chimichurri Code", href: "/" }, mobileMenu: false }}
footer={{ variant: "minimal" }}
>
<slot />
</BaseLayout>4. Use the JSON-LD factories in src/seo/index.ts
// packages/<your-web>/src/seo/index.ts
import { homeAppJsonLd } from "@chimichurricode/web-app-kit/seo";
export function homeJsonLd(): Array<Record<string, unknown>> {
return homeAppJsonLd({
siteId: "simple-foo-tool",
name: "Simple Foo Tool",
description: "What this tool does in one sentence.",
applicationCategory: "DeveloperApplication",
});
}That's it. Your web inherits the canonical <head>, theme bootstrap, ecosystem chrome, analytics, and SEO conventions automatically. When the kit gets updated (e.g. a new required <head> tag), your web picks it up on the next npm install.
Component inventory
BaseLayout.astro
File: packages/web-app-kit/BaseLayout.astro
Purpose: The shared HTML shell for every Chimi web property. Composes <html> + <head> (meta, SEO, icons, theme bootstrap, analytics) + <body> with SiteHeader, <main> slot, and SiteFooter. All chrome and head composition is handled here — consumer webs wrap their content and pass props.
Import path:
import BaseLayout from "@chimichurricode/web-app-kit/BaseLayout.astro";Props (see @chimichurricode/web-app-kit/types for full types):
| Prop | Type | Default | Description |
|---|---|---|---|
| siteId | string | required | Site identifier used by analytics and SEO systems |
| title | string | required | Page <title> (suffix appended automatically) |
| description | string | "" | Meta description |
| recipe | Recipe | "canvas-quiet" | Active color recipe applied to <body> |
| lang | string | "es" | <html lang> attribute |
| seo | SEOOverrides | {} | JSON-LD, canonical override, noindex flag |
| header | HeaderConfig | required | { brand, links, mobileMenu } |
| footer | FooterConfig | required | { variant, columns, … } |
| analytics | boolean | true | Set false to disable analytics bootstrap |
Consumer sites:
| Site | Package |
|---|---|
| brand | packages/brand |
| blog | packages/blog |
| techconf | packages/techconf |
| talks | packages/talks |
| rfp | packages/rfp |
| All simple-* tools | packages/simple-* |
Minimal usage example:
---
import BaseLayout from "@chimichurricode/web-app-kit/BaseLayout.astro";
interface Props {
title: string;
description?: string;
}
const { title, description = "" } = Astro.props;
---
<BaseLayout
siteId="my-site"
title={title}
description={description}
recipe="canvas-quiet"
header={{
brand: { label: "My Site", emText: "Site", byline: "by Chimichurri Code", href: "/" },
links: [
{ href: "/about", label: "About" },
],
mobileMenu: true,
}}
footer={{ variant: "minimal" }}
>
<slot />
</BaseLayout>Migration from a hand-written shell
The kit is opt-in per web. Each migration is a single PR:
- Replace
astro.config.mjswithdefineWebApp({ … }). Verify dev server boots. - Replace
src/layouts/Base.astrowith the<BaseLayout>wrapper. Run a snapshot diff (build before/after, compare HTML body hashes) — expect zero functional diff. - (Optional) Replace per-site
homeJsonLd()boilerplate withhomeAppJsonLd/homeEditorialJsonLd.
See openspec/changes/archive/2026-…-extract-web-app-kit/ for the full design rationale.
What stays in your web
The kit owns only what's invariant. Each web continues to author:
- Per-site nav links (passed as
header.links). - Per-site footer columns / brand description (passed as
footer.columns/footer.brandName/ etc.). - Per-site copy strings (
src/lib/copy.ts). - Per-site CSS (
src/styles/global.css). - Per-site pages and components.
- Per-site
tsconfig.json(extends the future roottsconfig.base.jsonfrom C6).
If the kit's API doesn't fit your web (e.g. you need a one-off <script> in <head>), keep the wrapper layer in your Base.astro and add what you need around <BaseLayout>.
Conventions enforced by the kit
- Recipe: every web declares one default recipe (passed as
recipeprop, defaults tocanvas-quiet). Pages may override per-page via<BaseLayout recipe="paper">. - Lang:
<html lang>defaults to"es"(overridable vialangprop). - Analytics: bootstraps
@chimichurricode/analyticswithsite = siteId,adapter = vercel | console(based onimport.meta.env.PROD),perf = true. Disable viaanalytics={false}. <main id="main">: always rendered, so skip-link patterns work out of the box.
Adoption status
Run npm run check:web-app-kit-adoption (after C2 ships) to see which webs consume the kit.
