Issues with rendering standard text in Svelte PortableText v2 component
I can help you work through these related issues with @portabletext/svelte! Let me break down what's happening and how to fix it.
Issue 1 & 2: Component Configuration Structure
The main problem is that you have block and listItem incorrectly placed inside the types object. In @portabletext/svelte, the component configuration works like this:
typesis for custom block types (yourcta,image,vimeo, etc.)marksis for inline annotations (like yourlink)block,list, andlistItemshould be top-level properties in thecomponentsobject, not nested undertypes
Here's the corrected structure:
<PortableText
value={post.body}
components={{
types: {
// Only custom types here
cta: Cta,
code: Code,
image: ImageBlock,
authorReference: AuthorBlock,
button: Button,
vimeo: Vimeo,
embedHTML: EmbedHTML,
},
marks: {
link: Link,
},
block: {
// Optional: custom renderers for different block styles
normal: Block,
h1: Block,
h2: Block,
// etc.
},
list: {
// Optional: custom list container components
bullet: YourBulletListComponent,
number: YourNumberListComponent,
},
listItem: {
bullet: ListItem,
number: ListItem,
// Note: no "normal" - that's a block style, not a list item style
}
}}
/>About the "Unknown list item style 'normal'" error: This happens because you had listItem nested under types with a normal property. List items don't have a "normal" style - that's a block style for regular paragraphs. The Portable Text specification shows that listItem is a property on blocks (like "listItem": "bullet") to indicate the block is part of a list, not a separate style type. Once you move listItem to the top level of your components configuration and remove normal from it, this error will disappear.
The simplest solution: If you don't need custom styling for blocks and lists, just remove those configurations entirely and let the library use its defaults:
<PortableText
value={post.body}
components={{
types: {
cta: Cta,
code: Code,
image: ImageBlock,
authorReference: AuthorBlock,
button: Button,
vimeo: Vimeo,
embedHTML: EmbedHTML,
},
marks: {
link: Link,
}
// That's it! Default rendering for blocks and lists
}}
/>Why Paragraphs with Links Are Disappearing
If you keep your custom Block component, make sure it properly renders its children. Your Block component must accept and render the text/children passed to it:
<!-- block.svelte --> <script> export let portableText; export let value; </script> <p> <slot /> </p>
The <slot /> is critical - without it, any inline content (including text with your Link mark component) won't render. This is why paragraphs with links are disappearing.
My recommendation: Unless you absolutely need custom block styling, remove the Block component configuration entirely and let @portabletext/svelte handle it with its defaults. This is the most reliable approach.
Issue 3: SvelteKit Hydration Problem
This is a separate SvelteKit issue unrelated to Portable Text. The symptom - the wrapper appearing on client-side navigation but disappearing on direct page load - indicates a hydration mismatch between server-rendered HTML and client-side expectations.
Check your src/app.html file. The standard SvelteKit structure should look like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>If you've manually added <div id="svelte">, it can cause hydration conflicts. Your options:
- Use
display: contentson any wrapper (as shown above) - this makes the div transparent to layout - Remove the wrapper entirely and use
%sveltekit.body%directly in the body tag - Move your wrapper to a layout component:
<!-- src/routes/+layout.svelte --> <div id="svelte"> <slot /> </div>
This ensures the wrapper is part of your Svelte component tree and will hydrate consistently.
Summary
- Move
blockandlistItemout oftypesto be top-level properties incomponents - Remove
normalfromlistItem(or better yet, remove custom block/listItem configs entirely) - Ensure your Block component has
<slot />or remove it completely - Fix your app.html by using
display: contents, removing custom wrappers, or moving them to+layout.svelte
These changes should resolve all three issues! The first two fixes will solve your Portable Text rendering problems, and the third will fix the SvelteKit hydration issue.
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.