Discussion on rendering links in Portabletext with @portabletext/svelte and potential solutions

15 replies
Last updated: Apr 29, 2022
Hello all, I think I asked this question earlier in the year and someone answered it and I then promptly forgot to note it down and its now disappeared as it was a few months back.
Basically, links are not rendering in portable text - as in a standard piece of text, highlighted and a link attached to it. The text is being displayed but the
a
tag is not.
I always under the impression that this works out of the box on Portabletext renderers but
@portabletext/svelte
doesn't seem to.
Anyone have any ideas what I need to add?
AI Update

You're right that links not rendering in @portabletext/svelte is a common issue! The key thing is that you need to provide custom components for marks like links – they don't render automatically out of the box.

The Solution

The @portabletext/svelte package requires you to pass custom components through the components prop (not serializers as some older examples might show). Here's how to handle link rendering:

<script>
  import {PortableText} from '@portabletext/svelte'
  import Link from './Link.svelte' // Your custom link component
</script>

<PortableText 
  value={yourBlocks} 
  components={{
    marks: {
      link: Link
    }
  }}
/>

Then create your Link.svelte component:

<script lang="ts">
  import type {MarkComponentProps} from '@portabletext/svelte'
  import {Snippet} from 'svelte'
  
  interface Props {
    portableText: MarkComponentProps<{href?: string}>
    children: Snippet
  }
  
  let {portableText, children}: Props = $props()
  let {value} = $derived(portableText)
</script>

{#if value.href}
  <a href={value.href}>
    {@render children()}
  </a>
{:else}
  {@render children()}
{/if}

Important Notes

For Svelte 5+: The library requires Svelte 5.0.0 or higher and uses the modern runes syntax ($props(), $derived()) as shown above. The components prop structure is the current standard.

Your schema is fine: The standard Portable Text block type in Sanity Studio already includes link annotations by default – the issue is purely on the rendering side with how @portabletext/svelte handles marks.

Why links need custom components: The library doesn't provide default implementations for annotation marks (like links with href data), only for decorator marks (like strong or em). This is by design since link rendering often needs customization (external link icons, target="_blank", routing library integration, etc.).

You can check out the official svelte-portabletext repository for more examples of customizing marks, blocks, and other components!

Hi
user U
- Sorry completely missed your reply.
Just to confirm not adding anything outside of the standard eg:


{
  title: 'Site Intro',
  name: 'intro',
  type: 'array',
     of: [{ type: 'block' }],
},
And then using
@portabletext/svelte
on the front end. In the past I've always used React when deploying Sanity and the above combined with react portabletext plugin installed and basic links always worked fine.
So my main question would be what, potentially, do I need to add to make it work. I hope there is a simple solution otherwise I might have to go back to using React and such...

Many thanks!
user U
- Further to this just for experimentation I added the following to the snippet:

    {
      title: 'Site Intro',
      name: 'intro',
      type: 'array',
      of: [{ type: 'block' }], 
      marks: {
        annotations: [
          {
            name: 'link',
            type: 'object',
            title: 'link',
            fields: [
              {
                name: 'url',
                type: 'url'
              }
            ]
          },
        ]
      }
    },
Which has had no effect - but I am getting an error that I hadn't noticed before:


PortableText] Mark of type link has no compatible renderer or is missing markDefs (block 70ab28e32ffb, child 21f3fc6da55e)
Don't know if that might help at all!
user P
I’d like to play with some more on my end. I would start by comparing the methods in the sanity sveltekit starter if you’d like to take a look at it yourself.
You know what
user U
- I'm using that starterkit and it didn't occur to me to look at those bits...
Silly me! I bet that will solve it, will report back.

And thanks!


https://media.giphy.com/media/2YgATu4877PZ29iwpZ/giphy.gif
oh great!
user U
- Will let you know!
Hey
user P
👋
I've just pushed a candidate release for v1 that adds a default link and fixes a few issues and API quirks with the v0.2 the starter uses. If this is blocking you, you can try out the beta v1 version by installing
@portabletext/svelte@1.0.0-next.1
and following the migration guide here .
Here to help if it doesn't go out great
🙂
Thank you
user U
for coming in and helping, sorry for the delay on my answer!
user B
you’re a rock star!
I built a project from the template just earlier. I’ll give this update a go, thank you!
You're the one helping everyone out 😉
If you do have the time and desire, perhaps you could update the starter as well? Glad to assist if you're interested!
user U
/
user B
- Rather sheepishly I should tiptoe in and tell you that I just tried Julia's advice and replicated the portable text snippet from the blog section and the links rendered just fine eg

<PortableText 
  blocks="{settings.intro}" 
  serializers={{
    marks: {
     link: Link
  }
}}/>
So a
massive oversight on my part.
But equally positives are coming out of my blustering around...I will have a look into those!


💐 I hugely appreciate both of your time and patience here. 💐
Thanks
I made a pass with Henrique’s instructions and was no bueno. But I’m certain that I didn’t follow them completely.
I also found a little error in the schema. I moved the post body object out to its own object component. It had two ‘types’ defined. You may want to do this - and it might be nice with the layout to add the native hotspot feature to both the post image field and portable text:
{ type: _'image'_, hotspot: _true_ },

My
failed fork/branch attempt You might also want want to check out some of the PRs on the original for some potential svelte and image handling improvements.
There are some pretty extensive updates in the pipe behind the scenes, so a lot of these little fixes may take a bit to get patched as it’s the calm before the storm right now for a lot of upcoming work. Please keep posting here with anything neat that you come across!
Svelte is really fun (so are all the react stacks like next, gatsby, remix, etc). I can’t speak to how well it scales, but I know a number of orgs who have switched to it for their products. I think the ‘beta/prerelease’ state it still feels like it’s in keeps a good bit of adoption from happening instead of others. We’re supporting the community here the best we can. If most folks are coming in with next or gatsby projects, we’ll have a lot more traffic on those frameworks. And we’re each humans with a variety of finite time to spend learning each thing.
Vercel makes a fantastic ecosystem, so naturally a good bit of traffic goes there and discovers next. I have high hopes that their recent investments in svelte will see some returns to the open core project. I’d also love to see a lot of the best things from react, svelte, vue, etc (and even django, .net, and loads of others) cherry pick the best from each other to make great new stuff. It’s a banquet of awesome.
This team is amazing. This community is amazing. Lots of new great stuff on the horizon!
(genuinely, with the big S tag I feel like I need to point out when praise isn’t a sponsored message, and this is not sponsored ^_^ )
I’d also like to point out that at its core, Sanity is a tool. And you don’t get a new screwdriver and expect it to remodel your kitchen for you. The studio and plugins are open source - go play, break, make new ones. Come up with wacky ideas, try hard to execute on them, and see what comes out of it. &lt;3

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.

Was this answer helpful?