Add “Building a federated blog” tutorial (Astro + Bun + SQLite)#695
Add “Building a federated blog” tutorial (Astro + Bun + SQLite)#695dahlia wants to merge 38 commits intofedify-dev:mainfrom
Conversation
Adds the first two chapters of the new "Building a federated blog" tutorial (docs/tutorial/astro-blog.md): - Chapter 1 (Introduction): describes what we're building, the target audience, and the list of features and limitations. - Chapter 2 (Setting up): walks through installing Bun and the fedify CLI, running fedify init with the Astro + Bun + in-memory options, and verifying the project works with fedify lookup. Also adds the tutorial to the navigation in docs/.vitepress/config.mts so it appears in the sidebar. The companion example repository is at: https://github.com/fedify-dev/astro-blog Assisted-by: Claude Code:claude-sonnet-4-6
Adds chapter 3 "Building the blog" to docs/tutorial/astro-blog.md. This chapter walks through: - Defining a content collection with Astro's Content Layer API (defineCollection + glob() loader) and a Zod schema. - Creating three sample Markdown posts in src/content/posts/. - Writing a minimal Layout.astro with global CSS that readers can copy-paste without needing an external CSS framework. - Implementing the blog listing page (src/pages/index.astro) using getCollection() and the filter/sort pattern. - Implementing the individual post page using dynamic routing and the render() helper. Also includes two screenshots of the working blog UI taken with Playwright. Also excludes .playwright-mcp/ from deno fmt and git tracking. Assisted-by: Claude Code:claude-sonnet-4-6
…rial Covers the in-memory key pair store (globalThis trick for Astro HMR), the actor dispatcher returning a Person with RSA + Ed25519 keys, inbox and followers stub dispatchers, content negotiation via @fedify/astro middleware, and the HTML profile page at /users/blog. Includes a screenshot of the actor profile page and instructions for verifying with fedify lookup. Assisted-by: Claude Code:claude-sonnet-4-6
Covers using fedify tunnel to expose the local dev server publicly, the X-Forwarded-Proto fix needed so actor URIs use https://, configuring Vite's allowedHosts for tunnel hostnames, and verifying the actor is discoverable from Mastodon's search interface. Assisted-by: Claude Code:claude-sonnet-4-6
Covers Follow/Undo inbox handlers with auto-accept, storing followers in the in-memory map, returning real follower data from the followers dispatcher, and displaying the follower count on the home page. Testing instructions use ActivityPub.Academy. Assisted-by: Claude Code:claude-sonnet-4-6
Covers migrating the in-memory Map store to bun:sqlite so that key pairs and followers survive server restarts. Explains key serialization (PKCS#8/SPKI via Web Crypto) and the bun-types TypeScript integration. Assisted-by: Claude Code:claude-sonnet-4-6
Chapter 8 covers the startup post-sync mechanism: compares the Astro content collection against a SQLite `posts` table and sends Create(Article), Update(Article), or Delete(Article) activities to followers. Also explains the Article object dispatcher and how @fedify/astro handles HTML/ActivityPub content negotiation on the same URL. Also adds `twoslash` to all TypeScript code blocks in Chapters 4–7 and `// @noErrors` where imports cannot be resolved in the VitePress context. Assisted-by: Claude Code:claude-sonnet-4-6
Covers handling Create/Update/Delete(Note) inbox activities, verifying the inReplyTo points at a local Article, and displaying stored comments below each post page. Includes XSS warning about rendering remote HTML. Assisted-by: Claude Code:claude-sonnet-4-6
…rial Chapter 10 covers: HTML sanitization for comment XSS prevention, Update(Person) for profile changes, Delete(Article) for deleted posts, image attachments, and Fly.io deployment notes. Also records the new tutorial in CHANGES.md under the 2.2.0 unreleased Docs section, referencing issue fedify-dev#691. Assisted-by: Claude Code:claude-sonnet-4-6
The previous text directed readers to use a Mastodon instance without specifying one, and the screenshot was taken unauthenticated (so the searched actor didn't appear in results). ActivityPub.Academy is a sandbox Mastodon instance already used in Chapters 6 and 9, so using it throughout keeps the tutorial consistent. The screenshot (mastodon-search.png) still needs to be retaken from ActivityPub.Academy while logged in. Assisted-by: Claude Code:claude-sonnet-4-6
First-time readers may be confused by the unusual sign-up flow: no email or password is required, just a checkbox and a button. Add a brief explanation and a screenshot of the sign-up page so readers know what to expect. Assisted-by: Claude Code:claude-sonnet-4-6
Assisted-by: Claude Code:claude-sonnet-4-6
Show what the blog profile looks like on ActivityPub.Academy instead of a plain-text approximation. Assisted-by: Claude Code:claude-sonnet-4-6
…API link Convert three bold paragraph headers (Key serialization, BLOB return type, Synchronous followers) into a proper list with italic terms and colons. Also link "Web Crypto API" to its MDN page. Assisted-by: Claude Code:claude-sonnet-4-6
…Chapter 9 Assisted-by: Claude Code:claude-sonnet-4-6
Assisted-by: Claude Code:claude-sonnet-4-6
Briefly explain what Fly.io and Fly Volumes are, and frame the section as an option rather than an assumption. Assisted-by: Claude Code:claude-sonnet-4-6
- Add X-Forwarded-Host handling to the middleware code blocks (Chapter 5
and 8); previously only X-Forwarded-Proto was shown
- Add security.allowedDomains: [{}] to the astro.config.ts code block and
explain why it is required for X-Forwarded-Host to be trusted
- Remove the ORIGIN env var approach; instead instruct users to open the
tunnel URL in their browser so the first request already carries the
correct forwarded headers
- Expand the Chapter 9 "Testing with ActivityPub.Academy" section with
step-by-step instructions and four new screenshots:
- activitypub-academy-timeline.png
- activitypub-academy-reply-box.png
- activitypub-academy-reply-typed.png
- post-with-comment.png
Assisted-by: Claude Code:claude-sonnet-4-6
VitePress TOC and heading styles were broken because all chapter headings were marked as h1 with ==== underlines. Only the document title should be h1; chapter-level headings should be h2 with --- underlines, matching the convention used in docs/tutorial/microblog.md. Assisted-by: Claude Code:claude-sonnet-4-6
Prevents Hongdown's sentence-case formatter from capitalizing "bun-types" to "Bun-types" in headings. Assisted-by: Claude Code:claude-sonnet-4-6
Cross-references like "Chapter 6" are opaque without numbered headings. Replace each with an italicized, linked section title so readers can navigate directly to the referenced content. Assisted-by: Claude Code:claude-sonnet-4-6
Assisted-by: Claude Code:claude-sonnet-4-6
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new, extensive Astro+Bun tutorial for a federated blog, registers it in docs navigation and CHANGES.md, adds Changes
Sequence Diagram(s)sequenceDiagram
participant Startup as "Startup Sync"
participant Astro as "Astro Server (actor/inbox/outbox)"
participant DB as "SQLite (blog.db)"
participant Follower as "Remote Follower Inbox"
Startup->>Astro: Read site posts & metadata
Astro->>DB: Load stored posts/follower list
Startup->>DB: Compute diffs (Create/Update/Delete)
loop For each follower
Startup->>Follower: POST Activity (Create/Update/Delete Article)
Follower-->>Startup: 2xx / error
Startup->>DB: Record delivery/result
end
sequenceDiagram
participant Remote as "Remote Actor"
participant Inbox as "Astro Inbox Route"
participant DB as "SQLite (blog.db)"
participant Renderer as "Static Post Page"
Remote->>Inbox: POST Activity (Follow / Create(Note) / Undo / Update / Delete)
Inbox->>DB: Validate, persist follower/comment changes
DB-->>Inbox: OK
Inbox->>Renderer: (on read) render comments/follower count from DB
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@codex review |
|
/gemini review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f4f759523
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive tutorial titled "Building a federated blog," which guides users through creating an ActivityPub-enabled blog using Astro and Fedify. The tutorial covers essential topics such as actor implementation, follower management, SQLite persistence, activity publishing, and comment handling. Additionally, the PR updates the project configuration and changelog to reflect these documentation improvements. I have no feedback to provide.
Fedify's key validation code (sig/key.ts) throws a TypeError if the key is not extractable, and it also needs to export the public key to serialize it in the actor document. Setting extractable: false breaks federation entirely after the first restart. Reverted both importKey() calls back to extractable: true. Assisted-by: Claude Code:claude-sonnet-4-6
|
@codex review |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive tutorial for building a federated blog using Astro and Fedify, covering actor setup, follower management, SQLite persistence, and ActivityPub activity handling. The review feedback suggests improving the code examples by using the more idiomatic 'followers' collection identifier in sendActivity() calls, which simplifies recipient handling and ensures proper addressing via the cc field.
Astro.params.slug is typed as string | undefined even after the 404 guard, so passing it directly to getCommentsByPost() produces a TypeScript type error. post.id is the same value but typed as string since it comes from the content collection entry. Assisted-by: Claude Code:claude-sonnet-4-6
|
@codex review |
|
/gemini review |
|
Codex Review: Didn't find any major issues. Another round soon, please! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive tutorial on building a federated blog using Astro and Fedify, covering actor setup, follower management, SQLite persistence, and activity handling. Supporting changes include updates to the project's configuration files and changelog. The review feedback highlights two critical issues in the tutorial's code snippets: a runtime "TypeError" caused by attempting to overwrite the read-only "APIContext.request" property in Astro middleware, and a schema validation failure in the "astro.config.ts" example due to an incorrect "security.allowedDomains" configuration.
2chanhaeng
left a comment
There was a problem hiding this comment.
What a great tutorial! I just got a little stuck in a few sections, so making some small adjustments there might be helpful.
Astro 7 will remove the re-export of z from astro:content. Import z from astro/zod directly as the package itself recommends. Assisted-by: Claude Code:claude-sonnet-4-6
Vite's dev server does not recognize bun: protocol imports as built-in modules, so it fails to resolve bun:sqlite when a request first hits server-side code. Setting vite.ssr.external tells Vite to skip bundling this import and let Bun's native module resolver handle it instead. Assisted-by: Claude Code:claude-sonnet-4-6
- Update fedify init console output to match current prompt order and
option text (web framework → package manager → message queue →
key-value store; removes the now-absent runtime question)
- Move the Visual Studio Code section to before the project verification
steps so readers open their editor before running commands; add
recommendation for the Astro VS Code extension
- Fix code-highlight range from {9-17} to {9-18} in the astro.config.ts
example so the full vite block is highlighted
- Add explicit import instructions before the setObjectDispatcher code
block so readers know to import Article and getCollection
Assisted-by: Claude Code:claude-sonnet-4-6
|
Pre-release has been published for this pull request: DocumentationThe docs for this pull request have been published: |
Closes #691.
Read it at https://6359de12.fedify.pages.dev/tutorial/astro-blog.
Background
The existing microblog tutorial demonstrates building a multi-user social server with Hono and Node.js. This new tutorial covers a different workflow: layering ActivityPub federation onto a content-first blog where posts are authored as static Markdown files in Astro content collections and served via Astro's server-side rendering (SSR) mode.
The target audience is developers who already have a blog (or want one) and want to make it federable—not ActivityPub implementors building a general-purpose social server.
What's included
A new end-to-end tutorial at docs/tutorial/astro-blog.md (≈ 2,900 lines, 10 chapters) and a companion example project at https://github.com/fedify-dev/astro-blog, with each chapter corresponding to a tagged commit in the example repo.
The tutorial covers:
fedify initand Astro's content collectionsPersonactor with Ed25519 + RSA-PKCS1-v1.5 key pairsFollow/Undo(Follow)inbox handlers and a followers collectionMaptobun:sqlitefor key pairs and followerspostsSQLite table and fans outCreate(Article),Update(Article), orDelete(Article)to all followersCreate(Note)/Update(Note)/Delete(Note)inbox activities as post comments and displaying them on the post pageSupporting changes:
bun-typesadded toproper_nounsin .hongdown.toml.playwright-mcp/**added tofmt.excludein deno.json (Playwright MCP auto-generates screenshots into this directory; they should not be reformatted bydeno fmt).playwright-mcp/added to .gitignore for the same reason