Rendering posts
New to this? Copy the prompt and paste it into Claude Code — it walks you through the whole setup and tells you what to put where.
Every post arrives in three forms. Pick the one that matches how much control you want; most sites want the first.
post.html — finished markup
Pre-rendered semantic HTML with syntax highlighting already applied. No renderer to ship, nothing to hydrate, and it works in any framework.
import { RillArticle } from '@rillpage/sdk/article'
import '@rillpage/sdk/styles.css'
export default async function PostPage({ params }) {
const post = await rill.posts.get((await params).slug)
if (!post) notFound()
return (
<article>
<h1>{post.title}</h1>
{post.subtitle && <p>{post.subtitle}</p>}
<RillArticle post={post} />
</article>
)
}RillArticle renders the cover image, the content, and optional JSON-LD. It deliberately does not render the title or byline — those belong to your page template, where your own typography applies.
Doing it by hand
<div
className="rill-prose"
dangerouslySetInnerHTML={{ __html: post.html }}
/>That is safe here, and it is worth knowing why. post.html is not author-supplied markup: Rill generates it from a document constrained to a fixed schema — headings, lists, tables, code, images, links. No <script> can reach that string, so sanitising it again would be theatre.
post.doc — your own components
The ProseMirror document, if you want a custom code block with a copy button, or images in a lightbox. Walk doc.content and map node types to components. More work; total control.
post.markdown — round-tripping
What the MCP server reads and writes. Useful for exports and migrations; not intended for display.
Next: Styling