
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeI can help you work through these related issues with @portabletext/svelte! Let me break down what's happening and how to fix it.
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:
types is for custom block types (your cta, image, vimeo, etc.)marks is for inline annotations (like your link)block, list, and listItem should be top-level properties in the components object, not nested under typesHere'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
}}
/>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.
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:
display: contents on any wrapper (as shown above) - this makes the div transparent to layout%sveltekit.body% directly in the body tag<!-- src/routes/+layout.svelte --> <div id="svelte"> <slot /> </div>
This ensures the wrapper is part of your Svelte component tree and will hydrate consistently.
block and listItem out of types to be top-level properties in componentsnormal from listItem (or better yet, remove custom block/listItem configs entirely)<slot /> or remove it completelydisplay: contents, removing custom wrappers, or moving them to +layout.svelteThese 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 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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store