Decide to render a partial or not dynamically in Astro
By Flavio Copes
Learn how to decide whether an Astro route returns an HTML partial or a full page, by toggling export const partial and wrapping the output in a Layout.
You can mark an Astro route as a partial with export const partial = true, and then decide at render time whether to wrap the output in a layout. Wrapped, it’s a full page. Unwrapped, it’s a fragment.
Using htmx I have the need to render an HTML partial from a page, so I use:
---
export const partial = true
---
This tells Astro to skip the <!DOCTYPE html> declaration and the <head> content for this route. The response is just the fragment, which is exactly what htmx wants when it swaps a piece of the page.
What if, however, I decide I want to render a full page instead, maybe depending on the HTTP method used to reach this page, or on who’s asking? Maybe htmx fetches this route to update a list, but a user can also open the same URL directly in the browser, and a bare fragment looks broken there.
export const partial = true is static. You can’t flip it per request. But you don’t need to. A full page is just HTML that includes the doctype, the head and the body, and that’s what a <Layout> component renders. So I keep the route marked as a partial, and choose whether to wrap the content in the layout.
Detecting who’s asking
htmx sends an HX-Request: true header with every request it makes, which makes the check easy:
---
import Layout from '../layouts/Layout.astro'
export const partial = true
const isHtmx = Astro.request.headers.get('HX-Request') === 'true'
---
{
isHtmx ? (
<ul>
<li>First comment</li>
</ul>
) : (
<Layout title="Comments">
<ul>
<li>First comment</li>
</ul>
</Layout>
)
}
When htmx calls the route, it gets the bare <ul>. When a browser navigates to it, it gets the full page, DOCTYPE, head tag and all.
Writing the fragment twice gets old fast. Extract it into its own component, and render that component in both branches.
One thing to watch: reading headers, or Astro.request.method, only works with on-demand rendering. In a static build the page is generated once at build time, and there’s no request to inspect. So this technique needs the route to be server-rendered, otherwise the check always takes the same branch.