6 Tutorial: Creating a Book from Your Blog
This tutorial shows how to compile your blog posts into a print-ready PDF book using Camp’s Typst integration. We’ll create a book configuration, write a render function, and build a PDF containing selected posts from your site.
This tutorial assumes you have a working Camp site with a blog collection. If you don’t have one yet, work through the Building a Camp Site tutorial first.
6.1 How Book Publishing Works
Camp’s book publishing is intentionally minimal. Camp handles:
Selecting pages from your site’s collections based on filters you define
Organizing them into parts and chapters
Passing them to your render function
Writing the output to a .typ file and calling typst compile
Everything else—
Before starting, make sure you have Typst installed. You can install it via your package manager (brew install typst on macOS) or download it from the Typst website.
6.2 Creating the Book Configuration
Book configurations use the #lang camp/book language. Create a file called "essays.book.rkt" in your site’s root directory:
"essays.book.rkt"
#lang camp/book render-with = "(mysite/render render-book)" output-folder = "_output" includes = ["template.typ"] [[parts]] name = "Essays" collections = ["posts"] date-from = 2024-01-01 date-to = 2024-12-31
The configuration has three required fields:
render-with: A readable datum specifying the module path and identifier of your render function. The function receives a list of parts and returns a complete Typst document as a string.
output-folder: Where to write the .typ and .pdf files.
parts: At least one part, each with a name and content sources.
The optional includes field lists files or directories to copy to the output folder before Typst compilation. Use this for templates, fonts, or images referenced by your Typst code.
6.3 Writing a Render Function
Your render function receives a list of parts. Each part is a hash with 'name (string) and 'chapters (list). Each chapter is a hash with 'slug (string) and 'doc (a Punct document).
Create "render.rkt" (or add to your existing one):
"render.rkt"
#lang racket/base (require camp punct/doc racket/string) (provide render-book) (define (render-book parts) (string-append ;; Preamble: import template and configure the book #<<TYPST #import "template.typ": book #show: book.with( title: "Selected Essays", authors: ("Your Name",), year: 2024, ) TYPST ;; Render each part (string-append* (for/list ([part (in-list parts)]) (render-part part))))) (define (render-part part) (define name (hash-ref part 'name)) (define chapters (hash-ref part 'chapters)) (string-append (format "= ~a\n\n" name) (string-append* (for/list ([ch (in-list chapters)]) (render-chapter ch))))) (define (render-chapter ch) (define slug (hash-ref ch 'slug)) (define doc (hash-ref ch 'doc)) (define title (or (meta-ref doc 'title) slug)) (string-append ;; Chapter heading with label for cross-references (format "== ~a <~a>\n\n" title slug) ;; Render the document body to Typst (camp-doc->typst doc) "\n\n"))
The key function is camp-doc->typst, which converts a Punct document to Typst markup. Your render function controls everything else: the preamble, part headings, chapter headings, and how content is assembled.
6.4 Writing a Typst Template
The template controls your book’s visual design. Create "template.typ":
"template.typ"
#let book(
title: none,
authors: (),
year: none,
body,
) = {
set document(title: title, author: authors.join(", "))
set text(font: "Linux Libertine", size: 11pt)
set page(paper: "us-trade", margin: (inside: 0.875in, outside: 0.625in))
set par(leading: 0.65em, first-line-indent: 1.5em, justify: true)
// Title page
align(center + horizon)[
#text(size: 24pt, weight: "bold")[#title]
#v(1em)
#text(size: 14pt)[#authors.join(" and ")]
#v(2em)
#text(size: 12pt)[#year]
]
pagebreak()
outline(title: "Contents")
pagebreak()
// Chapter headings
show heading.where(level: 1): it => {
pagebreak(weak: true)
v(2em)
text(size: 18pt, weight: "bold", it.body)
v(1em)
}
show heading.where(level: 2): it => {
v(1.5em)
text(size: 14pt, weight: "bold", it.body)
v(0.5em)
}
body
}
Your render function imports this template and applies it using #show: book.with(...).
All book metadata (title, authors, etc.) flows through the render function into the template—
6.5 Organizing Your Book
Parts can include content from collections (filtered by date and taxonomy) or explicit pages:
[[parts]] |
name = "2024 Essays" |
collections = ["posts"] |
date-from = 2024-01-01 |
date-to = 2024-12-31 |
taxonomies = { series = "essays" } |
|
[[parts]] |
name = "Technical Articles" |
collections = ["posts"] |
taxonomies = { tags = ["programming", "racket"] } |
|
[[parts]] |
name = "Appendix" |
pages = ["appendix-a.poly.pm", "appendix-b.poly.pm"] |
6.5.1 Filtering Content
Parts that pull from collections can filter pages by:
Date range: date-from and date-to select posts within a time period
Taxonomies: The taxonomies table filters by metadata values. A string value requires an exact match; an array matches if any value is present.
For example, to include posts tagged with either "programming" or "racket":
taxonomies = { tags = ["programming", "racket"] } |
6.6 Building the Book
Build your book with:
raco camp print essays.book.rkt
This command:
Loads your site configuration
Gathers and filters pages into parts and chapters
Copies includes to the output folder
Calls your render function
Writes the result to a .typ file
Runs typst compile to produce a PDF
The output files use the book file’s name (e.g., "_output/essays.typ" and "_output/essays.pdf").
6.7 Handling Custom Elements
If your blog posts use custom elements, you can handle them in two ways:
In your render function: Pass a custom render class to camp-doc->typst that handles your elements specially.
In your Typst template: Define Typst functions for elements like term and page-ref:
#let term(body) = emph(body) |
#let term_definition(body, name: none) = strong(body) |
For page references, the Typst output includes labels (e.g., <my-post>) that you can reference with @my-post or #link(<my-post>)[custom text].
6.8 Tips for Book Production
Blog posts often need adjustments for print:
Links: External URLs don’t work in print. Consider footnotes with URLs or styling links to show the URL inline.
Images: Web images may need higher resolution for print.
Length: Very short posts may feel sparse as chapters. Consider grouping related short posts.
See the Typst documentation for details on advanced formatting, running headers, page numbers, and other print production features.