DEV Community

Cover image for All about Astro
Alex Nguyen
Alex Nguyen

Posted on • Edited on • Originally published at alexnguyen.co.nz

All about Astro

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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");
---
Enter fullscreen mode Exit fullscreen mode

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"
/>
Enter fullscreen mode Exit fullscreen mode
export default defineConfig({
  image: {
    domains: ["fastly.picsum.photos"],
    // or if you need a wildcard
    remotePatterns: [{
      protocol: 'https',
      hostname: '**.picsum.photos'
    }]
  }
});
Enter fullscreen mode Exit fullscreen mode

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
---
Enter fullscreen mode Exit fullscreen mode

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"; 
Enter fullscreen mode Exit fullscreen mode

Change this to:

import { Gallery } from "./Gallery"; // React
import { Gallery } from "./Gallery.vue"; // Vue
import { Gallery } from "./Gallery.svelte"; // Svelte
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
<button>
  <slot />
</button>
Enter fullscreen mode Exit fullscreen mode

The same behaviour also happens with named slots.

<Button>
  <Fragment slot="text">Button Text</Fragment>
</Button>
Enter fullscreen mode Exit fullscreen mode
<button>
  <slot name="text" />
</button>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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; }'
Enter fullscreen mode Exit fullscreen mode

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;
}) {}
Enter fullscreen mode Exit fullscreen mode

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);
---
Enter fullscreen mode Exit fullscreen mode
---
const config = await Astro.glob("../../astro.config.mjs");
console.log("Astro config", config[0].default);
---
Enter fullscreen mode Exit fullscreen mode
const config = import.meta.glob("../../astro.config.mjs");

for (const path in config) {
  config[path]().then((mod) => {
    console.log(mod.default);
  });
}
Enter fullscreen mode Exit fullscreen mode

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,
  },
  // ...
};
Enter fullscreen mode Exit fullscreen mode
import config from "/src/config.ts";
import react from "@astrojs/react";

export default defineConfig({
  ...config,
  integrations: [
    react(),
  ], 
  // ...
});

Enter fullscreen mode Exit fullscreen mode
---
import config from "../config.ts";
console.log("config", config);
---
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)