Interact with my blogs by signing in
Interact with my blogs by signing in
When I started building my SvelteKit project with Strapi CMS, I quickly ran into a frustrating roadblock.
The Problem: No Native Strapi Support for Svelte
When I started building my SvelteKit project with Strapi CMS, I quickly ran into a frustrating roadblock. Strapi's rich text editor outputs content in a structured JSON format (blocks), but the official @strapi/blocks-react-renderer package only works with React. As a fresh Svelte 5 and SvelteKit user, I found myself without a proper solution to render Strapi's rich content.
The options were limited:
I chose option 3, and sbr-mike (Svelte Blocks Renderer) was born.
The Journey: Porting from React to Svelte 5
Porting the React blocks renderer to Svelte 5 came with its own set of challenges and learning experiences:
Challenge 1: Understanding the Block Structure
Strapi's rich text content comes as an array of block objects:
[
{
type: 'heading',
level: 1,
children: [{ type: 'text', text: 'My Title', bold: true }]
},
{
type: 'paragraph',
children: [
{ type: 'text', text: 'This is ' },
{ type: 'text', text: 'bold text', bold: true },
{ type: 'text', text: ' and ' },
{ type: 'text', text: 'italic text', italic: true }
]
}
];
Each block can contain nested children with various text modifiers (bold, italic, underline, strikethrough, inline code).
Challenge 2: Adapting to Svelte 5's New Syntax
Svelte 5 introduced runes ($state, $props, $derived) and snippets, which required rethinking how components receive and render children:
React approach:
function Paragraph({ children }) {
return <p>{children}</p>;
}
Svelte 5 approach with snippets:
<script lang="ts">
let { children }: { children: Snippet } = $props();
</script>
<p>{@render children()}</p>
Challenge 3: Context Management
The React version used React Context to pass block and modifier components down. In Svelte 5, I implemented this using Svelte's createContext:
import { createContext } from 'svelte';
export const [getRenderCTX, setRenderCTX] = createContext<{
blocks: Record<string, any>;
addMissingBlockType: (type: string) => void;
}>();
The Solution: sbr-mike
After overcoming these challenges, I successfully created a lightweight, fully-typed Svelte 5 blocks renderer that works seamlessly with Strapi.
Installation
bun add sbr-mike
# or
npm i sbr-mike
# or
pnpm add sbr-mike
# or
yarn add sbr-mike
Basic Usage
1. Simple Content Rendering
The most basic use case - rendering Strapi content in your SvelteKit page:
<script lang="ts">
import { BlocksRenderer } from 'sbr-mike';
import type { BlocksContent } from 'sbr-mike';
// This would typically come from your Strapi API
const content: BlocksContent = [
{
type: 'heading',
level: 1,
children: [{ type: 'text', text: 'Welcome to My Blog' }]
},
{
type: 'paragraph',
children: [
{ type: 'text', text: 'This is a ' },
{ type: 'text', text: 'simple example', bold: true },
{ type: 'text', text: ' of the blocks renderer.' }
]
}
];
</script>
<BlocksRenderer {content} />
2. Real-World Example: Fetching from Strapi
Here's how you'd use it with actual Strapi data in SvelteKit:
// src/routes/blog/[slug]/+page.ts
export async function load({ params, fetch }) {
const response = await fetch(
`https://your-strapi-api.com/api/articles?filters[slug][$eq]=${params.slug}&populate=*`
);
const data = await response.json();
return {
article: data.data[0]
};
}
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import { BlocksRenderer } from 'sbr-mike';
import type { BlocksContent } from 'sbr-mike';
let { data } = $props();
const content: BlocksContent = data.article.attributes.content;
</script>
<article class="prose lg:prose-xl">
<h1>{data.article.attributes.title}</h1>
<BlocksRenderer {content} />
</article>
3. All Supported Block Types
The renderer supports all common Strapi block types with text modifiers including ✅ bold, ✅ italic, ✅ <u>underline</u>, ✅ ~~strikethrough~~, and inline code. It also supports:
✅ Headings (h1-h6)
✅ Paragraphs
✅ Links
✅ Lists (ordered and unordered)
✅ Quotes
✅ Code blocks
✅ Images
4. Customizing Components
One of the most powerful features is the ability to replace default components with your own:
<script lang="ts">
import { BlocksRenderer } from 'sbr-mike';
import type { BlocksContent } from 'sbr-mike';
// Your custom components
import CustomHeading from './CustomHeading.svelte';
import CustomParagraph from './CustomParagraph.svelte';
import CustomBold from './CustomBold.svelte';
const content: BlocksContent = [/* your content */];
// Override default block components
const customBlocks = {
heading: CustomHeading,
paragraph: CustomParagraph
};
// Override default modifier components
const customModifiers = {
bold: CustomBold
};
</script>
<BlocksRenderer {content} blocks={customBlocks} modifiers={customModifiers} />
5. TypeScript Support
The package is fully typed, making it easy to work with in TypeScript projects with comprehensive type definitions for all block types.
6. Working with Tailwind
The renderer works perfectly with Tailwind CSS. The default components use minimal styling, so you can easily wrap the renderer in a container with Tailwind's typography plugin for beautiful, consistent styling.
7. Error Handling
The renderer gracefully handles missing or unsupported block types by logging warnings to the console and continuing to render the rest of the content.
Key Features
✅ Svelte 5 Native - Built with modern runes syntax ($state, $props, $derived) and snippets
✅ Full TypeScript Support - Comprehensive type definitions for all block types
✅ Lightweight - No heavy dependencies, just pure Svelte components
✅ Customizable - Replace any block or modifier component with your own
✅ Complete Coverage - Supports all Strapi block types (headings, paragraphs, lists, quotes, code, images, links)
✅ Text Modifiers - Bold, italic, underline, strikethrough, and inline code
✅ Tailwind Compatible - Works seamlessly with Tailwind CSS v4
✅ Error Resilient - Gracefully handles missing components and continues rendering
Performance Tips
Lazy Loading Images: Consider using Svelte's loading="lazy" for images in a custom image component
Code Syntax Highlighting: Integrate a syntax highlighter like Prism or Shiki in your custom code block component
Caching: Cache your Strapi responses in SvelteKit's load functions
Conclusion
Porting the Strapi blocks renderer from React to Svelte 5 was a challenging but rewarding experience. It not only solved my immediate problem but also gave me deep insights into:
If you're building a SvelteKit project with Strapi, I hope sbr-mike saves you the time and frustration I experienced. The package is open-source, MIT licensed, and ready to use in production.
Resources
NPM Package:
GitHub Repository:
Strapi Documentation:
Svelte 5 Documentation: