Embedding Claude Code sessions in blog posts

I wanted a way to share Claude Code sessions in blog posts. Not screenshots. Not copy-pasted text. The actual terminal experience - dark background, tool calls, the whole thing.

This post is about building that. And it uses the component it describes.

The idea

I was setting up this blog with Claude Code when the thought hit: what if I could embed these sessions directly?

claude
> my end goal is to be able to render chat like snapshots of this exact environment, so it should look like a terminal, lets brainstorm, any lib for that? should we just implement a component?
Good idea for an AI/agents blog. A few options:
 
Existing libs:
- termynal - lightweight, animated typing effect, but more for commands than chat
- asciinema - full terminal recordings, but overkill for static snapshots
- xterm.js - actual terminal emulator, way too heavy
 
Custom component (my recommendation):
- Build a simple Astro component that mimics the Claude Code look
- Dark bg, monospace, styled user/assistant turns
- Maybe tool calls get a different treatment
- Full control over styling to match your blog aesthetic

Custom component it is.

Finding the format

Claude Code stores sessions locally. I didn’t know the format, so we went looking.

claude
> id like to be able to input real chat sessions (as they are stored locally, or parts of them
That changes things - we'd parse the actual session format rather than authoring manually.
 
Let me find where Claude Code stores sessions and what the format looks like:
Bash(command: "ls -la ~/.claude/")
(output hidden)
Bash(command: "head -20 ~/.claude/projects/.../session....")
(output hidden)
This is great. The session format is clear:
 
Location: ~/.claude/projects/{project-path}/{session-id}.jsonl
 
Format: JSONL (one JSON object per line) with these types:
- type: "user" - User messages
- type: "assistant" - Assistant responses containing:
- type: "text" - The actual response text
- type: "thinking" - Extended thinking blocks
- type: "tool_use" - Tool calls with name and input

JSONL. One JSON object per line. Each message has a type (user/assistant), content blocks for text, tool calls, and thinking. Clean enough to parse.

Building it

Two files:

  1. parseSession.ts - Reads the JSONL, filters out metadata, deduplicates streamed messages
  2. ChatSession.astro - Renders the parsed messages with terminal styling
claude
> yes
Write(file_path: "src/utils/parseSession.ts")
(output hidden)
Write(file_path: "src/components/ChatSession.astro")
(output hidden)

The component accepts a session prop in three formats: typed arrays (cleanest), imported JSONL files, or inline strings.

The result

claude
> FUCKING AMAZING.
Hell yes.
 
What we built:
- src/utils/parseSession.ts - Parses Claude Code JSONL sessions
- src/components/ChatSession.astro - Renders them as terminal UI

Usage with typed arrays (recommended):

<ChatSession session={[
  { role: "user", content: "hello" },
  { role: "assistant", content: "Hi there!", tools: [{ name: "Read", input: { file_path: "foo.ts" } }] }
]} />

What’s next

The component is basic. Could add:

  • Actual tool output (file contents, command results)
  • Collapsible long outputs
  • Syntax highlighting in code blocks

But it works. I can now embed real sessions, not reconstructions.