Understanding Astro integrations and the hooks lifecycle
Tags: astro, webdev
Published: 2023-11-08 18:52:56

**Written by [Ohans Emmanuel](https://blog.logrocket.com/author/ohansemmanuel/)✏️**

With just a few lines of code, Astro integrations let you add new functionality and behavior to an Astro project. However, to effectively write Astro integrations, we must understand the Astro hooks lifecycle. 

These hooks are how we plug into Astro’s internal build process and specify custom behavior. This article serves as a guide to every hook, including where they should be used and how to create a custom integration that logs all relevant hooks in a sample Astro build process. 

_Jump ahead:_

*   [What are Astro integrations?](#astro-integrations)
*   [The Astro hooks lifecycle](#astro-hooks-lifecycle)
*   [How to order and trigger hooks](#order-trigger-hooks)
    *   [`astro:config:setup`](#astro-config-setup)
    *   [`astro:config:done`](#astro-config-done)
    *   [In development mode](#development-mode)
    *   [In production](#production)
*   [Examining the hooks evaluation order](#examining-hooks-evaluation-order)

## What are Astro integrations? <a name="astro-integrations">

By definition, integrations extend your Astro project with new functionality and behavior. Most of the integrations you build will be to support a particular feature, such as the [official sitemap integration](https://docs.astro.build/en/guides/integrations-guide/sitemap/) that generates a sitemap when you build your Astro project. 

Once you sufficiently understand how to build feature integrations, you‘ll be able to transfer your knowledge to building libraries or renderer integrations, such as the official [React](https://github.com/withastro/astro/tree/main/packages/integrations/react), [Preact](https://github.com/withastro/astro/tree/main/packages/integrations/preact), [Vue](https://github.com/withastro/astro/tree/main/packages/integrations/vue), or [Tailwind](https://github.com/withastro/astro/tree/main/packages/integrations/tailwind) integrations for Astro.

## The Astro hooks lifecycle <a name="astro-hooks-lifecycle">

In a general sense, “lifecycle” refers to the series of changes in the life of an organism. For example, a butterfly starts as an egg, then turns into a larva, a pupa, and ultimately a full-blown adult butterfly. Until human cloning becomes available, there’s a decent chance you also started as an infant, then became a toddler, went through puberty, and finally entered adulthood. At least, I hope so! 

In software, “lifecycle” refers to the different stages of a given process. With Astro hooks, we explicitly refer to the stages Astro goes through while building your application pages. 

This process covers resolving the Astro configuration setup, spinning up a local server, and bundling your pages statically or rendering them on the server side in production. 

To productively develop custom integrations, we’ll need to know where in the lifecycle we want to effect a change or reaction. Hooks are functions that are called at various stages of the build; to interact with the build process, we can use the following ten hooks:

*   `astro:config:setup`
*   `astro:config:done`
*   `astro:server:setup`
*   `astro:server:start`
*   `astro:server:done`
*   `astro:build:start`
*   `astro:build:setup`
*   `astro:build:generated`
*   `astro:build:ssr`
*   `astro:build:done`

Ten seems like a lot to remember, but you don’t have to memorize these. Instead, we’ll focus on understanding how they work; you can always refer to the official [reference](https://docs.astro.build/en/reference/integrations-reference/#hooks) when needed.

## How to order and trigger hooks <a name="order-trigger-hooks">

One of the first questions I asked myself when I started tinkering with Astro integrations was, “When exactly are these triggered, and is there some order of execution to them?” 

Let’s consider the following diagram, which depicts the order in which the hooks are executed: ![The order in which hooks are executed in development and production modes](https://blog.logrocket.com/wp-content/uploads/2023/10/order-hooks-execution-development-production.png) Two hooks kick off the process:

1.  `astro:config:setup`
2.  `astro:config:done`

These hooks are always executed, regardless of the Astro build process. Let’s breakdown when these are executed and how to leverage them in custom integrations.

### `astro:config:setup` <a name="astro-config-setup">

Think about being the first one at the pub, before it opens. You can cause a ruckus before anyone else even shows up! 

Similarly, this hook is executed when Astro is initialized. This happens before the Astro project or Vite config is resolved. The `astro:config:setup` hook is where we swoop in to extend the project configuration. In this hook, we can update the Astro config, apply Vite plugins, add component renderers, and inject scripts long before Astro starts building the application.

### `astro:config:done` <a name="astro-config-done">

Like a perfect pint of beer, we patiently wait to grab the glass until after it’s been poured. Similarly, this hook indicates when the Astro config has been resolved. When this hook triggers, it means every `astro:config:setup` hook has been invoked for every integration in the project. 

After the Astro config has finally got its act together and all the other integrations have done their thing, we can retrieve the final config and use it in our integration. 

Once we fire `astro:config:done`, there are two branches to consider: development and production mode.

### In development mode <a name="development-mode">

Take a look at the flowchart above again. When developing your apps locally — without initiating a production build typically via `npm run build` or `astro build` — the order of hooks execution in our app will follow the left side of the flowchart. That flow triggers the following hooks:

*   `astro:server:setup`
*   `astro:server:start`
*   `astro:server:done`

These hooks are executed when building your app for local development. 

Here’s a breakdown of when these are executed and how we could leverage these in our custom integrations:

<table>

<thead>

<tr>

<th>Hook</th>

<th>Executed when …</th>

<th>Why use this?</th>

</tr>

</thead>

<tbody>

<tr>

<td>`astro:server:setup`</td>

<td>We create the Vite server in development mode, before the `listen()` server event is fired, i.e., before starting the server</td>

<td>We can update the Vite server options and middleware. The Vite dev server object is passed as an argument to our hook.</td>

</tr>

<tr>

<td>`astro:server:start`</td>

<td>We fire the Vite `listen()` method, i.e., the server is running</td>

<td>We can jump in here to intercept network requests at the specified dev server address (passed as an argument to our hook)</td>

</tr>

<tr>

<td>`astro:server:done`</td>

<td>We closed the dev server</td>

<td>This is where we run cleanups. If you wish to clean up any side effects triggered during `astro:server:setup` or `astro:server:start`, now is a good time!</td>

</tr>

</tbody>

</table>

### In production <a name="production">

When we run a production build, two hooks will always be triggered. These are:

*   `astro:build:start`
*   `astro:build:setup`

Here’s a breakdown of when these execute and how we can leverage them in our custom integrations:

<table>

<thead>

<tr>

<th>Hook</th>

<th>Executed when …</th>

<th>Why use this?</th>

</tr>

</thead>

<tbody>

<tr>

<td>`astro:build:start`</td>

<td>After the Astro config is completely resolved, but before the production build begins</td>

<td>The production build is about to start, but you can still set up some global objects or clients needed during the build</td>

</tr>

<tr>

<td>`astro:build:setup`</td>

<td>The build is just about to start and the build config is fully constructed</td>

<td>To steal the perfect phrase from the official Astro documentation: this is our final chance to modify the build. We can overwrite some defaults and make sure everything is looking top-notch.</td>

</tr>

</tbody>

</table>

I must mention that if you're not sure whether to use `astro:build:setup` or `astro:build:start`, go for `astro:build:start` instead. 

Now, depending on how the page is being built (statically generated or server-side-rendered), either `astro:build:generated` or `astro:build:ssr` will be invoked, respectively. Finally, we invoke `astro:build:done`. 

Here’s the final breakdown of when these are executed and how we can use them in our custom integrations:

<table>

<thead>

<tr>

<th>Hook</th>

<th>Executed when …</th>

<th>Why use this?</th>

</tr>

</thead>

<tbody>

<tr>

<td>`astro:build:generated`</td>

<td>The static production build has completely generated routes and assets.</td>

<td>We can access generated routes and assets before we clean up build artifacts. Per the official docs, this is uncommon, and we might be better off using `astro:build:done` in many cases, except when we really need to access the generated files before cleanup.</td>

</tr>

<tr>

<td>`astro:build:ssr`</td>

<td>A production SSR build completes</td>

<td>We get access to the SSR manifest, which is helpful when creating custom SSR builds.</td>

</tr>

<tr>

<td>`astro:build:done`</td>

<td>The production build completes!</td>

<td>This is where we may access the generated routes and assets, so we can copy them or transform them. To transform generated assets, consider using a Vite plugin and configuring `astro:config:setup`</td>

</tr>

</tbody>

</table>

## Examining the hooks evaluation order <a name="examining-hooks-evaluation-order">

Let’s try some practice examples. Go ahead and write out a simple integration that, when invoked, spits out a log to the server console. Then, you can tinker with building several pages for production and inspect the logs. 

Our eventual goal is to have a custom integration that looks something like this:

```typescript
{
  name: "some-identifier",
  hooks: {
    "hook-name": () => {
    // log hook name so we know it's been invoked
    }
  }
}
```

Let’s go ahead and build this out. Create a new Astro application with the following custom integration:

```typescript
// 📂 src/integrations/lifecycle-logs.ts

import kleur from "kleur";
import type { AstroIntegration } from "astro";

// Create a new dateTimeFormat object
const dateTimeFormat = new Intl.DateTimeFormat([], {
  hour: "2-digit",
  minute: "2-digit",
  second: "2-digit",
});

export const lifecycleLogs = () => {
  const hooks = [
    `astro:config:setup`,
    `astro:config:done`,
    `astro:server:setup`,
    `astro:server:start`,
    `astro:server:done`,
    `astro:build:start`,
    `astro:build:setup`,
    `astro:build:generated`,
    `astro:build:ssr`,
    `astro:build:done`,
  ] as const;

  // base integration structure. "hooks" will be updated
  let integration: AstroIntegration = {
    name: "astro-lifecycle-logs",
    hooks: {},
  };

  // loop over the hooks list and add the name and log
  for (const hook of hooks) {
    integration.hooks[hook] = () => {
      // 👀 Get a new date string
      const date = dateTimeFormat.format(new Date());

      // log with kleur colours and formatting
      console.log(`${kleur.gray(date)} ${kleur
        .bold()
        .yellow("[lifecycle-log]")} ${kleur.green(hook)}
        `);
    };
  }

  return integration;
};�

export default lifecycleLogs;
```

Import `lifecycleLogs` and add it to your project’s integration list:

```typescript
// astro.config.mjs 
import { defineConfig } from "astro/config";
import { lifecycleLogs } from 'src/integrations/lifecycle-logs'

export default defineConfig({
  // 👀 invoke the imported lifecycleLogs function in the list
  integrations: [lifecycleLogs()],
});
```

Finally, (re)start your application to see the logs in the console as shown below: ![The dev lifecycle hooks](https://blog.logrocket.com/wp-content/uploads/2023/10/dev-lifecycle-hooks.png) As an exercise, I suggest you add a new SSR page and run a production build to see the order of hooks execution logged. Below is an example with two pages:

*   A static `index.astro` page
*   A server-side-rendered `ssr.astro` page

Now, observe the logs in the image below, and notice the `[lifecycle-log]` messages from `astro:config:setup` to `astro:build:done`. This represents the full lifecycle of the build process i.e., captured and logged. ![The entire hook lifecycle, logged](https://blog.logrocket.com/wp-content/uploads/2023/10/entire-hook-lifecycle-logged.png)

## Conclusion

Understanding Astro integrations and the hooks lifecycle is essential to building custom integrations. With your new knowledge of Astro’s ten hooks and their timings, you can go ahead and build more robust custom integrations. 

If you want to keep in touch with me, you can find all my contact information on my [personal site](https://www.ohansemmanuel.com/) or [LinkedIn](https://www.linkedin.com/in/ohans-emmanuel/). Cheers!

---

## Get set up with LogRocket's modern error tracking in minutes:

1. Visit https://logrocket.com/signup/ to get an app ID.
2. Install LogRocket via NPM or script tag. `LogRocket.init()` must be called client-side, not server-side.

NPM:
```bash
$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
```
Script Tag:
```javascript
Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
```

3.(Optional) Install plugins for deeper integrations with your stack:
*   Redux middleware
*   ngrx middleware
*   Vuex plugin

[Get started now](https://lp.logrocket.com/blg/signup)