Fixing Extra Whitespace in Astro Components

Inline components in Astro can introduce unwanted spaces in the rendered HTML. Here’s how wrapping a custom link component in a fragment removed the extra whitespace.

A few months ago, I switched this blog from Next.js to Astro. I like the extra control Astro gives me. While I still enjoy working with React and TSX, Astro is simply a better fit for this particular project.

The migration went smoothly. I was able to port all my components from React to Astro without any major issues.

The Whitespace Problem

After the migration, I noticed something strange: extra whitespace around some of my <Link> components. At first, I didn’t think much of it – browsers often ignore extra spaces in the markup.

For example, Astro generated something like this:

Lorem impsum <a href="/">dolor</a>  sit amet.

See that space after the link? It doesn’t usually show up, so it’s easy to miss. But when the link is at the end of a sentence, things change:

Lorem impsum <a href="/">dolor</a> .

Now, the space becomes visible, and it looks awkward.

Why this happens?

I don't think it's a bug in Astro – more like expected behavior. Astro doesn't (and shouldn't) remove the whitespace around components. When a component is used, it's treated more like a block than an inline element. This mindset works great for layouts and page sections, but gets confusing when you're using components in the middle of a text – like links. Let's be honest: when you write a text in plain HTML, you don't add newlines or additional spaces around links. You write something closer to foo <a href="...">bar</a> baz. For astro, the component is an independent part of markup, so whitespace stays – even if it's component used inline.

The Link Component

Here’s the simplified version of the Link.astro component I was using:

Link.astro
---
import type { HTMLAttributes } from 'astro/types';
import { IS_PROD } from '../contants';

type Props = HTMLAttributes<'a'> & {
  prefetch?: boolean;
};

const { prefetch = true, ...props } = Astro.props;
---

<a {...props} data-prefetch={IS_PROD ? prefetch : undefined}><slot /></a>

<script>
  document
    .querySelectorAll('a[href][data-prefetch="true"]')
    .forEach((link) => { /* prefetch logic */ });
</script>

Nothing fancy, just a basic anchor element and a prefetch script. Astro extracts the script, so it is only included once on the page. The component itself worked fine only that whitespace annoyed me.

I spent quite some time searching through Astro issues (this one looked promising: #6011). Nothing worked. At one point, I even tried rendering the component into a variable and trimming the result.

The Fix: Using a Fragment

Then, I had an idea: what if I wrap the anchor in a fragment (<>...</>)? Aaand, it worked!

Here's the updated Link component:

Link.astro
---
import type { HTMLAttributes } from 'astro/types';
import { IS_PROD } from '../contants';

type Props = HTMLAttributes<'a'> & {
  prefetch?: boolean;
};

const { prefetch = true, ...props } = Astro.props;
---

<>
  <a {...props} data-prefetch={IS_PROD ? prefetch : undefined}><slot /></a>
</>

<script>
  document
    .querySelectorAll('a[href][data-prefetch="true"]')
    .forEach((link) => { ... });
</script>

By wrapping the <a> tag in a fragment, the extra whitespace is gone. Problem solved.