After consolidating the article display we continue with the conversation between content, design, and feature implementation. In particular, the Mount Fyffe article would benefit from a hero image. This definitely needs to be content-driven - i.e. specified in mdx files.
There are options though. Should we do this as a frontmatter (within the hyphen-hyphen-hyphen part at the top of the file hero: ../path/to/image
) or within the body of the article (by rendering a Hero component <Hero src="ā¦" />
)?
One consideration that informs the decision is that presently the articles template enforces the structure of frontmatter information display like so (pseudo code):
<!-- [slug].astro -->
<article>
<header>
<h1>{post.data.title}</h1>
<p>{post.data.date}</p>
<p>{post.data.tags}</p>
</header>
<Content />
</article>
This means we canāt render anything from the article body prior to the header, meaning we couldnāt render a Hero component before the title, even if we initially wanted to go with that option.
This inflexibility begs the question āwhy use frontmatter at all?ā. Why not just let articles decide what goes where by importing components directly into the article body and have the template just render <Content />
? On the flipside this would certainly allow inconsistency between articles to creep in. I decide to stick with consistency. Frontmatter it is.
Besides some expected layout rework, importing images in frontmatter requires some special attention - we need to rework the config.ts
a little to expose an image zod helper to define an image type frontmatter field:
// config.ts
schema: ({ image }) => ({
hero: z.optional(
image().refine((img) => img.width >= 1080, {
message: "Hero image must be at least 1080 pixels wide!",
}))
})
})
Now you can use the configured frontmatter hero field directly as the src
prop value for Astroās Image
component:
<!-- [slug].astro -->
<Image src={post.data.hero} alt="" />
This is great. The only problem is that the hero image canāt be wider than the article display. Till now the Layout
component had:
<main>
<SiteWidthContainer>
<slot /> // hero needs to break out from here
</SiteWidthContainer>
</main>
This requires some rework to remove the global wrapper approach and add site width styling to every non image element within rendered mdx content. Hereās the idea as a tiny [slug].astro
file:
---
const { Content } = await post.render();
---
<header>ā¦</header>
<div class="mdx-content">
<Content />
</div>
<style>
.mdx-content > *:not(img) {
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
</style>
This breaks some stylings like left margin on ul
s etc, so requires some massaging until things are back to happy, but voila, the hero image has unrestrained size. Additionally images within articles now have the option to extend beyond the text width as well. Nifty, but not quite done.
Optimising Images With Astro
I set the max-width
on the images div container to 1200px
and spot that the hero image is taking a while to load. A quick scan of Astroās docs gives the headline message:
āUse Astroās nativeĀ
<Image />
Ā andĀ<Picture />
Ā components for optimised imagesā
ā¦but itās not quite as straightforward as that.
How it works is that these components provide a unified props API to control the solving of two problems:
- The generation of appropriately sized image files (e.g. on disk) ready for serving statically by a web server.
- Figuring out which of those image files is the minimum size required given a browser window of a certain size loading the page (via putting corresponding HTML properties on the img element).
Letās work this through with a few examples.
Firstly, specifying a static width like <Image width=ā200ā />
will cause Astro to:
- scale down the original source image to
200px
in width and make it ready to serve at a size specific url - set the output
img
src
property to that url.
For responsivity (i.e. when the displayed image width depends on the browserās viewport size) you can <Image widths={[800,1200,1600]} sizes=ā90vwā />
which will cause Astro to:
- generate three image files at 800, 1200, and 1600 pixels wide and make them ready to serve at size specific urls
- add a
srcset
property with three entries on the outputimg
element with the urls of the generated images - pass through the sizes property as provided for the browser to determine which image to load based on the viewport size.
All in all, this is certainly a nice abstraction to skip needing to generate images and wire up their urls but you do still need to understand responsive image concepts (aka srcset
& sizes
properties, dpi etc) and use Astroās api appropriately to get āoptimised imagesā in responsive cases.
I slapped on sufficiently suitable widths
and sizes
and the Hero image network size dropped 5x resulting in a better loading experience.
Heroās in articles. Yay!