Astro has recently become my favourite framework for building websites. It has a great developer experience, features, community, and it's frequently updated.
These are some findings or quirks I've noted when using it that don't warrant a separate blog post, things that you may encounter depending on the Astro version you're using. These may be things I've missed in the documentation, something I've done incorrectly (likely), or an actual issue or bug (less likely).
Upgrading to Astro 4.0
astro-icon with local images
Update to astro-icon@0.8.2 to be able to use local icons in an Astro 4.0 project.
View Transitions
client:only priority
client:only
is meant to have a high priority and behaves like client:load
(except server rendering is skipped), but any component using client:only
won't immediately show its static content (without hydration) resulting in a flash (unlike client:load
). This doesn't seem to change if your page is server rendered or static.
Selectors with page events
When selecting elements, make sure to call them inside the called function of the event listener, otherwise the reference will be stale when you get to the next page:
<script is:inline>
// this won't work after you navigate
// const menu = document.getElementById("menu");
function initMenu() {
const menu = document.getElementById("menu");
// ...
}
initMenu();
document.addEventListener("astro:after-swap", () => initMenu());
</script>
define:vars with lifecycle events
If you use define:vars to pass frontmatter variables into your script tag, everything inside will re-run everytime that variable changes:
---
const pathname = new URL(Astro.request.url).pathname;
---
<script define:vars={{ pathname }}>
// this will log for every pathname change, not just once on initial site load
document.addEventListener("astro:page-load", () => console.log("page-load"), {
once: true,
});
</script>
Issues with CSS animations
There is a visual issue I've encountered with Firefox when using <ViewTransitions />
with the default fallback (for browsers that don't support the View Transitions API yet) and a CSS opacity animation on page load.
The opacity was set to 0 and set to animate to 100% with a CSS animation but using View Transitions resulted in the animation never running (or the opacity not being set).
The workaround was to use a fallback of swap
:
<head>
<ViewTransitions fallback="swap" />
<!-- ... -->
</head>
This is something I'll look to investigate in the future.
Collections
Querying all collections
There is no built-in way to query all collections. One workaround to do this is using Astro.glob
to pull all markdown files:
---
const collections = await Astro.glob("../content/**/*.md");
---
The data returned from this will be different from the data returned from getCollection. If you needed it in the same structure, you could extract the folder names only using the data from Astro.glob
, and then loop over them with getCollection
.
Images
Remote images with subdomains
You need to specify the subdomain for any remote image links in astro.config.mjs
, otherwise it won't get optimized and the original source will be used:
<!-- resolves to https://fastly.picsum.photos/** -->
<Image
src="https://picsum.photos/200/300"
width="500"
height="500"
alt="remote image"
/>
export default defineConfig({
image: {
domains: ["fastly.picsum.photos"],
// or if you need a wildcard
remotePatterns: [{
protocol: 'https',
hostname: '**.picsum.photos'
}]
}
});
Content collections
When referencing images in content collections using the image
helper, you can also reference the image from the root directory (src
).
---
image: ./my-post.jpg # default
image: /src/content/posts/my-post.jpg
---
You may need to do this for compatibility with your CMS, for example, if it doesn't support reading relative paths for things like image previews.
Framework Components / Islands
Mixing frameworks
When using components from multiple frameworks together, not adding the extension may result in an error of This component likely uses @astrojs/react ...
:
import { Gallery } from "./Gallery";
Change this to:
import { Gallery } from "./Gallery"; // React
import { Gallery } from "./Gallery.vue"; // Vue
import { Gallery } from "./Gallery.svelte"; // Svelte
Alternatively (although I'm not sure why you would do this), you can force the component to hydrate for a particular framework with client:only to make it work without the extension.
Svelte without client directive
You must use a client directive for a Svelte component's slot to appear, even if this component doesn't need to hydrate.
<Button client:load>Button Text</Button>
<button>
<slot />
</button>
The same behaviour also happens with named slots.
<Button>
<Fragment slot="text">Button Text</Fragment>
</Button>
<button>
<slot name="text" />
</button>
You don't need to do this with React or Vue.
TypeScript
Props with client directives
You may get various type errors when using client directives with any props other than children
(not sure if this applies to other non-React frameworks):
<Fade client:visible delay={0.25}><h1>Title</h1></Fade>
Which results in:
Type '{ children: any; "client:visible": true; }' is not assignable to type 'IntrinsicAttributes & { delay: number; children: ReactNode; }.
Property 'delay' is missing in type '{ children: any; "client:visible": true; }' but required in type '{ delay: number; children: ReactNode; }'
A (bad) workaround is to mark these prop(s) as optional in your type or interface inside that component:
export function Fade({
delay,
children,
}: {
delay?: number;
children: React.ReactNode;
}) {}
There is likely a better solution as this hides the error even if the prop(s) isn't optional.
WebStorm
There are some issues resolving types with WebStorm with an active issue here.
One solution which solves a lot of the problems is using the bundled TypeScript setting. Open Settings > Preferences > Languages & Frameworks > TypeScript > Bundled.
Miscellaneous
Importing the Astro config
If you want to import your config from astro.config.mjs
elsewhere in your project, it will only work in development.
All of these methods will fail during build:
---
import config from "../../astro.config.mjs";
console.log("Astro config", config);
---
---
const config = await Astro.glob("../../astro.config.mjs");
console.log("Astro config", config[0].default);
---
const config = import.meta.glob("../../astro.config.mjs");
for (const path in config) {
config[path]().then((mod) => {
console.log(mod.default);
});
}
This is due to the use of any Astro imports like @astrojs/react
which I guess can't be properly parsed or serialized during build.
One workaround is to create a separate config file that doesn't use any Astro related imports which you import in astro.config.mjs
and anywhere else:
export default {
site: "example.com",
server: {
port: 3000,
},
// ...
};
import config from "/src/config.ts";
import react from "@astrojs/react";
export default defineConfig({
...config,
integrations: [
react(),
],
// ...
});
---
import config from "../config.ts";
console.log("config", config);
---
With this method you won't be able to read the functions you set, but if you wanted to, you could set additional values in the config object (config.ts
) which you import in astro.config.mjs
, and manually add the functions to the relevant key like integrations
, based on which ones exist.
Syntax or rehype plugins
To use a plugin like Rehype Pretty Code, you will need to disable the default Astro syntax highlighting in your astro.config.mjs
:
markdown: {
syntaxHighlight: false
}
Astro's highlighting runs last, so it has priority over any existing plugins.
Feel free to follow me or check out my blog.
Top comments (0)