keystatic
keystatic copied to clipboard
Astro images relative paths with nested content
I am trying to add Keystatic to a few Astro sites I manage. But there is one problem I just can not seem to fix asked in the Discord but tought this is a beter place because it is not a config error as far as i know.
I really like and depend on the Astro image component. But in order for the Astro image component to work the imports of the images need to be relative. And after doing some research that can work in Keystatic as well
With a config something like this:
images: {
directory: 'src/content/kvb1/images',
publicPath: '../../content/kvb1/images/',
},
I have content collections data like this:
When adding a image to the
intro.mdoc
the mdoc file in the root of the content collections it work fine without any problem.
Also Keystatic work fine.
But when trying to add a image to a mdoc file inside a nested folder the uploading works fine
The mdoc file show the images added as:

but for it to work i should be:

But now Keystatic does not work anymore
Also:
- when deleting a image that is in a nested folder the image get deleted but the folder structure is not
I hacked my way around it after watching this stream. Still has some problems but seams to work for now:
My key static config:
content: fields.document({
images: {
directory: 'src/assets/kvb1/images/',
publicPath: '/assets/kvb1/images/',
},
}),
When saving a image with key static it is stored in the assets folder. The markdoc image is added like this:

Then to get the astro image component working i made a custom markdoc image component:
---
import type {ImageMetadata} from 'astro';
const {src, alt, title, __optimizedSrc} = Astro.props
import {Image} from 'astro:assets';
const fixedSrc = '/src' + src // append /src/ because the glob import works from project root
const images = import.meta.glob<{ default: ImageMetadata }>('/src/assets/**/*.{jpeg,jpg,png,gif}') // use /**/* so it finds nested images
if (!images[fixedSrc]) throw new Error(`"${fixedSrc}" does not exist in glob: "src/assets/**.{jpeg,jpg,png,gif}"`);
---
<figure class="w-fit">
<Image src={images[fixedSrc]()} alt={alt} class="rounded"
widths={[800, 1600]}
sizes={`(max-width: 720px) 800px, (max-width: 1600px) 1600px, ${images[fixedSrc]().width}px`}/>
<figcaption class="text-center">{title}</figcaption>
</figure>
And then this custom image component is added to the mdoc config
nodes: {
image:{
...nodes.image,
render: component('./src/components/mdoc/Image.astro'),
attributes: {
src: { type: String },
alt: { type: String },
title: { type: String },
},
},
Some problems this solution still has:
- The glob import imports all images and does at 10-20% to the build time
- VScode and Webstorm do not like the image paths that keystatic use so editing outside key static is a bit broken
Hey friends!
After asking the question about nested collections and assets in the Astro Discord, I was pointed to this solution:
Define a path alias in your tsconfig.json
like so:
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
+ "baseUrl": ".",
+ "paths": {
+ "@assets/*": ["src/assets/*"]
+ }
}
}
...and then, you can set the publicPath
to @assets/...
for images, and... it will work!
image: fields.object({
file: fields.image({
label: 'Image',
directory: 'src/assets/images',
publicPath: '@assets/images',
}),
}),
This will bypass the problem of having to work out how to construct ../../../
segments based on the slug nesting 🔥