Skip to content

Writing a theme

A theme is an Astro project using the @gophenberg/astro kit. You write two files: an Astro config naming the integration, and a theme definition naming your layouts. The kit does the rest, including the routes.

The best starting point is copying the reference theme from the Gophenberg repository. This page walks its shape.

astro.config.mjs turns the project into a theme:

import { gophenberg } from '@gophenberg/astro/config'
import { defineConfig } from 'astro/config'
export default defineConfig({
integrations: [gophenberg()],
})

src/theme.ts declares what your theme provides:

import { defineTheme } from '@gophenberg/astro'
import Archive from './layouts/Archive.astro'
import Base from './layouts/Base.astro'
import NotFound from './layouts/NotFound.astro'
import Post from './layouts/Post.astro'
export default defineTheme({
layouts: { Base, Post, Archive, NotFound },
pagination: { perPage: 10 },
seo: { siteName: 'My Site' },
})

NotFound is optional, and a blocks map can join the object to override single block types, covered in rendering blocks.

You write no pages for posts. The integration injects the front page, /{type}/page/{n} listings, /{type}/{slug} posts, the 404 page, and the health route Gophenberg probes at startup. Each page route renders one of your layouts with the content already fetched.

Your own pages coexist with them: src/pages/about.astro serves /about as in any Astro site. To put posts on a page of your own, read the content API directly.

Base wraps every page. It receives seo and an optional title, and its one obligation is rendering GophenbergHead inside <head>, which emits the page title and the stylesheets that make block content look right:

---
import { GophenbergHead } from '@gophenberg/astro/components'
interface Props {
seo: { siteName: string }
title?: string
}
const { seo, title } = Astro.props
---
<html lang="en">
<head>
<GophenbergHead title={title ? `${title} | ${seo.siteName}` : seo.siteName} />
</head>
<body>
<header><a href="/">{seo.siteName}</a></header>
<main><slot /></main>
</body>
</html>

Post receives the post, your blocks overrides, and seo, and renders the content through the Blocks component:

---
import { Blocks } from '@gophenberg/astro/components'
import type { BlockComponentMap, Post } from '@gophenberg/astro'
import Base from './Base.astro'
interface Props {
post: Post
blocks?: BlockComponentMap
seo: { siteName: string }
}
const { post, blocks, seo } = Astro.props
---
<Base seo={seo} title={post.title}>
<article>
<h1>{post.title}</h1>
<Blocks html={post.content} components={blocks} post={post} />
</article>
</Base>

Archive receives posts (summaries without content), total, page, perPage, type, and seo. NotFound receives seo.

astro dev runs your theme against a running Gophenberg. It needs two addresses, one for content and one for the block stylesheets, which the dev server does not serve itself:

Terminal window
GOPHENBERG_API_URL=http://localhost:8081 \
GOPHENBERG_ASSET_ORIGIN=http://localhost:8081 \
pnpm astro dev

When it looks right, installing a theme takes it to production.