Skip to content

Default Theme

The @indiepub/theme-default package (at themes/default in the monorepo) is the reference implementation of an IndiePub site. It’s a complete, deployable Astro project — not an installable library.

What’s included

Pages

RouteDescription
/Home feed — recent articles and notes
/posts/[slug]Canonical entry URL for all post types
/notes/Notes index
/notes/[slug]301 redirect → /posts/[slug]
/articles/Articles index
/articles/[slug]301 redirect → /posts/[slug]
/photos/Photos index
/photos/[slug]301 redirect → /posts/[slug]
/bookmarks/Bookmarks index
/tags/[tag]Entries filtered by tag (matches tag name or slug)
/aboutAuthor h-card page
/now”What I’m doing now” page
/subscribeEmail subscription form
/loginMember magic-link login

Components

ComponentPurpose
EntryCardCompact post preview — adapts layout to post type
EntryDetailFull post display with webmention counts
HeaderSite navigation with theme toggle
SidebarAuthor h-card + tag cloud
HCardMicroformats2 h-card for author identity
NoteComposerInline quick-post form shown to logged-in admins
SubscribeFormEmail subscription widget
TagListTag cloud with entry counts
FeedLinks<link> discovery tags for RSS, Atom, JSON feeds
ThemeToggleLight/dark mode switch (persisted in localStorage)
SocialIconIcon renderer for social link profiles

Layouts

  • Base.astro — HTML shell, <head> (meta, OG tags, feed links, favicon), theme init script
  • Page.astro — Two-column layout with sidebar

Member visibility

Entries have a visibility field:

  • public — visible to everyone
  • unlisted — accessible by direct URL but not listed in feeds or indexes
  • members — requires a logged-in subscriber session; redirects to /login otherwise
  • private — returns 404 to all visitors including logged-in members

Pages pass memberTier (from Astro.locals.member?.tier) to content.getEntries() so the content API filters appropriately. The canonical entry page (/posts/[slug]) enforces visibility itself:

if (entry.visibility === 'private') return 404;
if (entry.visibility === 'members' && !Astro.locals.member) redirect('/login');

CSS theming

The theme uses a two-level CSS custom property system based on oklch:

/* Palette layer — tweak these in your site's CSS */
:root {
--brand-hue: 280; /* 0–360 */
--brand-chroma: 0.15; /* 0–0.4 */
}
/* Semantic layer — derived from palette, used in components */
:root {
--color-accent: oklch(55% var(--brand-chroma) var(--brand-hue));
--color-bg: oklch(98% 0.005 var(--brand-hue));
--color-surface-1: oklch(95% 0.008 var(--brand-hue));
/* … */
}

Override --brand-hue and --brand-chroma in a <style> block or global CSS file to retheme the entire site without touching any component CSS.

Cache headers

Pages use smart Cache-Control headers for Cloudflare’s CDN:

  • Public pages: s-maxage=60, stale-while-revalidate=3600
  • Logged-in admin: no-store (bypass CDN cache so compose button appears)
  • OG images: s-maxage=86400, stale-while-revalidate=604800

Getting started

The fastest path is cloning the monorepo and working from themes/default directly:

Terminal window
git clone https://github.com/your-org/indiepub
cd indiepub/themes/default
pnpm install
pnpm db:migrate # applies migrations to local D1
pnpm dev

Visit http://localhost:4321/admin/onboarding to finish setup.

Customising the theme

The default theme is meant to be forked and customised. Key files:

  • astro.config.mjs — integration config, syndication targets, media URL
  • wrangler.toml — D1/R2 bindings and bucket names
  • src/styles/ — CSS custom properties, typography scale
  • src/components/ — UI components
  • src/layouts/Base.astro<head> content, global structure
  • src/pages/about.astro — your personal about page
  • src/pages/now.astro — your “now” page

TypeScript notes

  • src/env.d.ts must explicitly declare App.Locals.indiepub/// <reference types="@indiepub/astro" /> does not propagate global augmentations across packages
  • @indiepub/types belongs in devDependencies; import types from there, not from @indiepub/core
  • exactOptionalPropertyTypes: true is enabled — optional props must use T | undefined not just T