Aller au contenu

Collections de contenus

Ajouté à la version : astro@2.0.0

Les collections de contenu est le meilleur moyen de gérer et de créer du contenu dans n’importe quel projet Astro. Les collections permettent d’organiser vos documents, de valider votre frontmatter et d’assurer automatiquement la sécurité des types TypeScript pour l’ensemble de votre contenu.

Une collection de contenu est un répertoire de premier niveau dans le répertoire réservé du projet src/content, comme src/content/newsletter et src/content/authors. Seules les collections de contenu sont autorisées dans le répertoire src/content. Ce répertoire ne peut pas être utilisé pour autre chose.

Une entrée de collection est un élément de contenu stocké dans votre répertoire de collection de contenu. Les entrées peuvent utiliser des formats de création de contenu tels que Markdown (.md) et MDX (.mdx) en utilisant l’intégration MDX) ou l’un des deux formats de données supportés : YAML (.yaml) et JSON (.json). Nous recommandons d’utiliser un schéma de nommage cohérent (minuscules, tirets au lieu d’espaces) pour vos fichiers afin de faciliter la recherche et l’organisation de votre contenu, mais ce n’est pas obligatoire. Vous pouvez également exclure des entrées de la construction en préfixant le nom du fichier avec un trait de soulignement (_).

  • Répertoiresrc/content/
    • Répertoirenewsletter/ la collection “lettre d’information”
      • week-1.md une entrée de la collection
      • week-2.md une entrée de la collection
      • week-3.md une entrée de la collection

Une fois que vous avez une collection, vous pouvez commencer à interroger votre contenu en utilisant les API de contenu intégrées d’Astro.

Astro stocke des métadonnées importantes pour les collections de contenu dans un répertoire .astro dans votre projet. Aucune action n’est nécessaire de votre part pour maintenir ou mettre à jour ce répertoire. Vous êtes encouragé à l’ignorer complètement lorsque vous travaillez sur votre projet.

Le répertoire .astro sera mis à jour automatiquement chaque fois que vous lancerez les commandes astro dev, astro build. Vous pouvez lancer astro sync à tout moment pour mettre à jour le répertoire .astro manuellement.

Si deux fichiers représentent différents types de contenu (par exemple, un article de blog et un profil d’auteur), il est fort probable qu’ils appartiennent à des collections différentes. Ceci est important car de nombreuses fonctionnalités (validation frontmatter, sécurité de type TypeScript automatique) exigent que toutes les entrées d’une collection partagent une structure similaire.

Si vous travaillez avec différents types de contenu, vous devez créer plusieurs collections pour représenter chaque type. Vous pouvez créer autant de collections différentes que vous le souhaitez dans votre projet.

  • Répertoiresrc/content/
    • Répertoirenewsletter/
      • week-1.md
      • week-2.md
    • Répertoireblog/
      • post-1.md
      • post-2.md
    • Répertoireauthors/
      • grace-hopper.json
      • alan-turing.json

Une collection de contenus est toujours un dossier de premier niveau à l’intérieur du répertoire src/content/. Vous ne pouvez pas imbriquer une collection dans une autre. Cependant, vous pouvez utiliser des sous-répertoires pour organiser votre contenu au sein d’une collection.

Par exemple, vous pouvez utiliser la structure de répertoire suivante pour organiser les traductions i18n dans une seule collection docs. Lorsque vous interrogez cette collection, vous pouvez filtrer les résultats par langue en utilisant le chemin d’accès au fichier.

  • Répertoiresrc/content/
    • Répertoiredocs/ cette collection utilise des sous-répertoires pour classer les documents par langue.
      • Répertoireen/
      • Répertoirees/
      • Répertoirede/

Pour tirer le meilleur parti de vos collections de contenus, créez un fichier src/content/config.ts dans votre projet (les extensions .js et .mjs sont également supportées.) Il s’agit d’un fichier spécial qu’Astro chargera et utilisera automatiquement pour configurer vos collections de contenus.

src/content/config.ts
// 1. Importer des propriétés à partir de `astro:content`
import { defineCollection } from 'astro:content';
// 2. Définie votre (vos) collection(s)
const blogCollection = defineCollection({ /* ... */ });
// 3. Exporter un objet `collections` unique pour enregistrer votre (vos) collection(s)
// Cette clé doit correspondre au nom du répertoire de votre collection dans "src/content"
export const collections = {
'blog': blogCollection,
};

Si vous n’étendez pas déjà les paramètres TypeScript recommandés par Astro strict ou strictest dans votre fichier tsconfig.json, vous devrez peut-être mettre à jour votre tsconfig.json pour activer strictNullChecks.

tsconfig.json
{
// Note: Aucune modification n'est nécessaire si vous utilisez "astro/tsconfigs/strict" ou "astro/tsconfigs/strictest"
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"strictNullChecks": true
}
}

Si vous utilisez des fichiers .js ou .mjs dans un projet Astro, vous pouvez activer IntelliSense et la vérification de type dans votre éditeur en activant allowJs dans votre tsconfig.json :

tsconfig.json
{
// Note: Aucune modification n'est nécessaire si vous utilisez "astro/tsconfigs/strict" ou "astro/tsconfigs/strictest"
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"strictNullChecks": true,
"allowJs": true
}
}

Définition d’un schéma de collection

Titre de la section Définition d’un schéma de collection

Les schémas assurent la cohérence des données de base ou d’entrée au sein d’une collection. Un schéma garantit que ces données existent sous une forme prévisible lorsque vous devez les référencer ou les interroger. Si un fichier ne respecte pas le schéma de sa collection, Astro affichera une erreur utile pour vous en informer.

Les schémas alimentent également les typages TypeScript automatiques d’Astro pour votre contenu. Lorsque vous définissez un schéma pour votre collection, Astro génère et applique automatiquement une interface TypeScript à ce schéma. Le résultat est un support TypeScript complet lorsque vous interrogez votre collection, y compris l’autocomplétion des propriétés et la vérification des types.

Pour définir votre première collection, créez un fichier src/content/config.ts s’il n’existe pas déjà (les extensions .js et .mjs sont également supportées) :

  1. Importer les utilitaires appropriés de astro:content.
  2. Définissez chaque collection que vous souhaitez valider. Cela inclut un type (introduit dans Astro v2.5.0) spécifiant si la collection contient des formats de création de contenu comme Markdown (type : 'content') ou des formats de données comme JSON ou YAML (type : 'data'). Il inclut également un schema qui définit la forme de votre frontmatter ou des données d’entrée.
  3. Exporter un seul objet collections pour enregistrer vos collections.
src/content/config.ts
// 1. Importer des propriétés à partir de `astro:content`
import { z, defineCollection } from 'astro:content';
// 2. Définie le `type` et le `schema` pour chaque collection
const blogCollection = defineCollection({
type: 'content', // v2.5.0 et plus
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
}),
});
// 3. Exporter un objet `collections` unique pour enregistrer votre ou vos collection(s)
export const collections = {
'blog': blogCollection,
};

Vous pouvez utiliser defineCollection() autant de fois que vous le souhaitez pour créer plusieurs schémas. Toutes les collections doivent être exportées à partir d’un seul objet collections.

src/content/config.ts
const blogCollection = defineCollection({
type: 'content',
schema: z.object({ /* ... */ })
});
const newsletter = defineCollection({
type: 'content',
schema: z.object({ /* ... */ })
});
const authors = defineCollection({
type: 'data',
schema: z.object({ /* ... */ })
});
export const collections = {
'blog': blogCollection,
'newsletter': newsletter,
'authors': authors,
};

Au fur et à mesure que votre projet grandit, vous êtes également libre de réorganiser votre base de code et de déplacer la logique hors du fichier src/content/config.ts. Définir vos schémas séparément peut être utile pour réutiliser les schémas dans plusieurs collections et pour partager les schémas avec d’autres parties de votre projet.

src/content/config.ts
// 1. Import les propriétés et schémas
import { defineCollection } from 'astro:content';
import { blogSchema, authorSchema } from '../schemas';
// 2. Définie votre collection
const blogCollection = defineCollection({
type: 'content',
schema: blogSchema,
});
const authorCollection = defineCollection({
type: 'data',
schema: authorSchema,
});
// 3. Exporter plusieurs collections pour les enregistrer
export const collections = {
'blog': blogCollection,
'authors': authorCollection,
};

Utilisation de schémas de collecte tiers

Titre de la section Utilisation de schémas de collecte tiers

Vous pouvez importer des schémas de collecte de n’importe où, y compris des paquets npm externes. Cela peut s’avérer utile lorsque vous travaillez avec des thèmes et des bibliothèques qui fournissent leurs propres schémas de collecte.

src/content/config.ts
import { blogSchema } from 'my-blog-theme';
const blogCollection = defineCollection({ type: 'content', schema: blogSchema });
// Exporter la collection de blogs, en utilisant un schéma externe de "mon-blog-thème".
export const collections = {
'blog': blogCollection,
};

Définition des types de données avec Zod

Titre de la section Définition des types de données avec Zod

Astro utilise Zod pour alimenter ses schémas de contenu. Avec Zod, Astro est capable de valider le frontmatter de chaque fichier dans une collection et de fournir des types TypeScript automatiques lorsque vous interrogez le contenu à partir de votre projet.

Pour utiliser Zod dans Astro, importez l’utilitaire z depuis "astro:content". Il s’agit d’une réexportation de la bibliothèque Zod, et il supporte toutes les fonctionnalités de Zod. Voir le README de Zod’s pour une documentation complète sur le fonctionnement de Zod et les fonctionnalités disponibles.

// Exemple : Une feuille de contrôle des types de données Zod les plus courantes
import { z, defineCollection } from 'astro:content';
defineCollection({
schema: z.object({
isDraft: z.boolean(),
title: z.string(),
sortOrder: z.number(),
image: z.object({
src: z.string(),
alt: z.string(),
}),
author: z.string().default('Anonymous'),
language: z.enum(['en', 'es']),
tags: z.array(z.string()),
// Une propriété optionnelle dans le frontmatter. Très courante !
footnote: z.string().optional(),
// Dans le frontmatter, les dates écrites sans guillemets sont interprétées comme des objets de Date !
publishDate: z.date(),
// Vous pouvez également transformer une chaîne de dates (par exemple "2022-07-08") en un objet Date.
// publishDate: z.string().transform((str) => new Date(str)),
// Avancé : Valider que la chaîne est bien un courriel
authorContact: z.string().email(),
// Avancé : Valider que la chaîne est bien une URL
canonicalURL: z.string().url(),
})
})

Définition des références de collection

Titre de la section Définition des références de collection

Les entrées d’une collection peuvent également “référencer” d’autres entrées connexes.

Avec la fonction reference() de l’API Collections, vous pouvez définir une propriété dans un schéma de collection comme une entrée d’une autre collection. Par exemple, vous pouvez exiger que chaque entrée space-shuttle comprenne une propriété pilot qui utilise le propre schéma de la collection pilot pour la vérification de type, l’autocomplétion et la validation.

Un exemple courant est un article de blog qui fait référence à des profils d’auteurs réutilisables stockés en JSON, ou à des URL d’articles connexes stockés dans la même collection :

import { defineCollection, reference, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
// Référencer un auteur unique de la collection `authors` par `id`
author: reference('authors'),
// Référence un tableau d'articles liés de la collection `blog` par `slug`id`.
relatedPosts: z.array(reference('blog')),
})
});
const authors = defineCollection({
type: 'data',
schema: z.object({
name: z.string(),
portfolio: z.string().url(),
})
});
export const collections = { blog, authors };

Cet exemple d’article de blog spécifie les slug des articles liés et l’id de l’auteur de l’article :

src/content/blog/welcome.md
---
title: "Welcome to my blog"
author: ben-holmes # references `src/content/authors/ben-holmes.json`
relatedPosts:
- about-me # references `src/content/blog/about-me.md`
- my-year-in-review # references `src/content/blog/my-year-in-review.md`
---

Lorsque vous utilisez type : 'content' , chaque entrée de contenu génère une propriété slug conviviale à partir de son file id. Cette propriété est utilisée pour interroger l’entrée directement à partir de votre collection. Il est également utile pour créer de nouvelles pages et URL à partir de votre contenu.

Vous pouvez remplacer la balise générée par une entrée en ajoutant votre propre propriété slug au fichier frontmatter. Ceci est similaire à la fonctionnalité “permalink” d’autres frameworks web. "slug" est un nom de propriété spécial et réservé qui n’est pas autorisé dans le schema de votre collection personnalisée et qui n’apparaîtra pas dans la propriété data de votre entrée.

---
title: Mon article de Blog
slug: mon-slug-personnalise/supports/slashes
---
Le contenu de votre article

Astro fournit deux fonctions pour interroger une collection et retourner une (ou plusieurs) entrée de contenu : getCollection() et getEntry().

import { getCollection, getEntry } from 'astro:content';
// Obtenir toutes les entrées d'une collection.
// Requiert le nom de la collection en tant qu'argument.
// Exemple : récupérer `src/content/blog/**`
const allBlogPosts = await getCollection('blog');
// Obtenir une seule entrée d'une collection.
// Requiert le nom de la collection et
// soit l'entrée `slug` (collections de contenu) soit `id` (collections de données).
// Exemple : récupérer `src/content/authors/grace-hopper.json`
const graceHopperProfile = await getEntry('authors', 'grace-hopper');

Les deux fonctions renvoient des entrées de contenu telles que définies par le type CollectionEntry.

Toutes les références définies dans votre schéma doivent être interrogées séparément après avoir interrogé votre entrée de collection. Vous pouvez utiliser à nouveau la fonction getEntry(), ou getEntries(), pour récupérer l’entrée référencée dans l’objet data retourné.

src/pages/blog/welcome.astro
---
import { getEntry, getEntries } from 'astro:content';
const blogPost = await getEntry('blog', 'welcome');
// Résoudre une référence unique
const author = await getEntry(blogPost.data.author);
// Résoudre un tableau de références
const relatedPosts = await getEntries(blogPost.data.relatedPosts);
---
<h1>{blogPost.data.title}</h1>
<p>Auteur: {author.data.name}</p>
<!-- ... -->
<h2>Vous pourriez également aimer:</h2>
{relatedPosts.map(p => (
<a href={p.slug}>{p.data.title}</a>
))}

Filtrer les requêtes sur les collections

Titre de la section Filtrer les requêtes sur les collections

getCollection() prend un callback optionnel “filter” qui vous permet de filtrer votre requête basée sur les propriétés id ou data (frontmatter) d’une entrée. Pour les collections de type : 'content', vous pouvez aussi filtrer en fonction de slug`.

Vous pouvez utiliser cette propriété pour filtrer selon n’importe quel critère de contenu. Par exemple, vous pouvez filtrer par des propriétés telles que draft pour empêcher la publication d’un brouillon d’article sur votre blog :

// Exemple : Filtrer les entrées de contenu avec `draft : true` (brouillon : vrai)
import { getCollection } from 'astro:content';
const publishedBlogEntries = await getCollection('blog', ({ data }) => {
return data.draft !== true;
});

Vous pouvez également créer des pages provisoires qui sont disponibles lors de l’exécution du serveur de développement, mais qui ne sont pas construites dans la production :

// Exemple : Filtrer les entrées de contenu avec `draft : true` uniquement lors de la construction pour la production
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
});

L’argument filter permet également de filtrer les répertoires imbriqués dans une collection. Puisque id inclut le chemin imbriqué complet, vous pouvez filtrer par le début de chaque id pour ne renvoyer que les éléments d’un répertoire imbriqué spécifique :

// Exemple : Filtrer les entrées par sous-répertoire dans la collection
import { getCollection } from 'astro:content';
const englishDocsEntries = await getCollection('docs', ({ id }) => {
return id.startsWith('en/');
});

Utilisation du contenu dans les modèles Astro

Titre de la section Utilisation du contenu dans les modèles Astro

Une fois que vous avez interrogé les entrées de votre collection, vous pouvez accéder à chaque entrée directement dans votre modèle de composant Astro. Cela vous permet de rendre du HTML pour des choses comme des liens vers votre contenu (en utilisant le contenu slug) ou des informations sur votre contenu (en utilisant la propriété data).

Pour plus d’informations sur le rendu de votre contenu en HTML, voir Rendre le contenu en HTML ci-dessous.

src/pages/index.astro
---
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog');
---
<ul>
{blogEntries.map(blogPostEntry => (
<li>
<a href={`/my-blog-url/${blogPostEntry.slug}`}>{blogPostEntry.data.title}</a>
<time datetime={blogPostEntry.data.publishedDate.toISOString()}>
{blogPostEntry.data.publishedDate.toDateString()}
</time>
</li>
))}
</ul>

Transmettre du contenu en tant que propriété

Titre de la section Transmettre du contenu en tant que propriété

Un composant peut également passer une entrée de contenu entière en tant que propriété.

Si vous faites cela, vous pouvez utiliser l’utilitaire CollectionEntry pour taper correctement les propriétés de vos composants en utilisant TypeScript. Cet utilitaire prend un argument de type chaîne qui correspond au nom du schéma de votre collection, et hérite de toutes les propriétés du schéma de cette collection.

src/components/BlogCard.astro
---
import type { CollectionEntry } from 'astro:content';
interface Props {
post: CollectionEntry<'blog'>;
}
// `post` correspondra au type de schéma de votre collection "blog".
const { post } = Astro.props;
---

Une fois la requête effectuée, vous pouvez rendre les entrées Markdown et MDX en HTML en utilisant la propriété de fonction render() de l’entrée. L’appel à cette fonction vous permet d’accéder au contenu rendu et aux métadonnées, y compris un composant <Contenu /> et une liste de tous les titres rendus.

src/pages/render-example.astro
---
import { getEntry } from 'astro:content';
const entry = await getEntry('blog', 'post-1');
const { Content, headings } = await entry.render();
---
<p>Publié le: {entry.data.published.toDateString()}</p>
<Content />

Générer des routes à partir du contenu

Titre de la section Générer des routes à partir du contenu

Les collections de contenu sont stockées en dehors du répertoire src/pages/. Cela signifie qu’aucune route n’est générée par défaut pour les éléments de votre collection. Vous devrez créer manuellement une nouvelle route dynamique pour générer des pages HTML à partir des entrées de votre collection. Votre route dynamique va mettre en correspondance le paramètre de la requête entrante (ex : Astro.params.slug dans src/pages/blog/[...slug].astro) pour récupérer la bonne entrée à l’intérieur d’une collection.

La méthode exacte de génération des routes dépendra de votre mode de construction output : ‘static’ (par défaut) ou ‘server’ (pour SSR).

Construire pour une sortie statique (par défaut)

Titre de la section Construire pour une sortie statique (par défaut)

Si vous construisez un site web statique (le comportement par défaut d’Astro), vous devez utiliser la fonction getStaticPaths() pour créer plusieurs pages à partir d’un seul composant src/pages/ pendant votre construction.

Appelez getCollection() à l’intérieur de getStaticPaths() pour interroger votre contenu. Ensuite, créez vos nouveaux chemins d’URL en utilisant la propriété slug de chaque entrée de contenu.

src/pages/posts/[...slug].astro
---
import { getCollection } from 'astro:content';
// 1. Génére un nouveau chemin pour chaque entrée de collection
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}
// 2. Pour votre modèle, vous pouvez obtenir l'entrée directement à partir de la propriété
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<h1>{entry.data.title}</h1>
<Content />

Cela générera une nouvelle page pour chaque entrée de la collection blog. Par exemple, une entrée dans src/content/blog/hello-world.md aura un slug de hello-world, et donc son URL finale sera /posts/hello-world/.

Construction pour la sortie serveur (SSR)

Titre de la section Construction pour la sortie serveur (SSR)

Si vous construisez un site web dynamique (en utilisant le support SSR d’Astro), vous n’êtes pas censé générer des chemins à l’avance pendant la construction. Au lieu de cela, votre page devrait examiner la requête (en utilisant Astro.request ou Astro.params) pour trouver le slug à la demande, et ensuite le récupérer en utilisant getEntry().

src/pages/posts/[...slug].astro
---
import { getEntry } from "astro:content";
// 1. Obtenir le slug de la requête du serveur entrant
const { slug } = Astro.params;
if (slug === undefined) {
throw new Error("Slug is required");
}
// 2. Recherche de l'entrée directement à l'aide du slug de la requête
const entry = await getEntry("blog", slug);
// 3. Redirection si l'entrée n'existe pas
if (entry === undefined) {
return Astro.redirect("/404");
}
// 4. (Facultatif) Rendre l'entrée en HTML dans le modèle
const { Content } = await entry.render();
---

Migration basé sur le routage des fichiers

Titre de la section Migration basé sur le routage des fichiers

Si vous avez un projet Astro existant, comme un blog, qui utilise des fichiers Markdown ou MDX dans les sous-dossiers de src/pages/, envisagez de migrer le contenu associé ou les fichiers de données vers des collections de contenus.

Voyez comment convertir un exemple de blog basique de src/pages/posts/ à src/content/posts dans notre tutoriel étape par étape qui utilise la base de code du projet fini du tutoriel Construire un blog.

Activation de la mise en cache de la construction

Titre de la section Activation de la mise en cache de la construction

Ajouté à la version : astro@3.5.0 Experimentale

Si vous travaillez avec de grandes collections, vous pouvez activer la mise en cache avec l’option experimental.contentCollectionCache. Cette fonctionnalité expérimentale optimise le processus de construction d’Astro, permettant aux collections inchangées d’être stockées et réutilisées entre les constructions.

Dans de nombreux cas, cela peut conduire à des améliorations significatives des performances de construction.

Pendant que cette fonctionnalité se stabilise, vous pouvez rencontrer des problèmes avec le cache stocké. Vous pouvez toujours réinitialiser votre cache de construction en exécutant la commande suivante :

npm run astro build -- --force

Astro supporte les plugins remark ou rehype qui modifient votre frontmatter directement. Vous pouvez accéder à ce frontmatter modifié à l’intérieur d’un contenu en utilisant la propriété remarkPluginFrontmatter renvoyée par render() :

---
import { getEntry } from 'astro:content';
const blogPost = await getEntry('blog', 'post-1');
const { remarkPluginFrontmatter } = await blogPost.render();
---
<p>{blogPost.data.title}{remarkPluginFrontmatter.readingTime}</p>
Méthode associée : Ajout du temps de lecture

Les pipelines remark et rehype ne s’exécutent que lorsque votre contenu est rendu, ce qui explique pourquoi remarkPluginFrontmatter n’est disponible qu’après avoir appelé render() sur votre entrée de contenu. En revanche, getCollection() et getEntry() ne peuvent pas retourner ces valeurs directement parce qu’ils ne rendent pas votre contenu.

Travailler avec des dates dans le frontmatter

Titre de la section Travailler avec des dates dans le frontmatter

Plusieurs formats de date sont possibles dans les collections de contenus, mais le schéma de votre collection doit correspondre au format utilisé dans votre texte de présentation YAML Markdown ou MDX.

YAML utilise la norme [ISO-8601] (https://www.iso.org/iso-8601-date-and-time-format.html) pour exprimer les dates. Utilisez le format yyyy-mm-dd (par exemple 2021-07-28) avec un type de schéma z.date() :

src/pages/posts/example-post.md
---
title: Mon article de Blog
pubDate: 2021-07-08
---

Le format de la date sera spécifié en UTC si aucun fuseau horaire n’est fourni. Si vous devez spécifier un fuseau horaire, vous pouvez utiliser le format ISO 8601.

src/pages/posts/example-post.md
---
title: Mon article de Blog
pubDate: 2021-07-08T12:00:00-04:00
---

Pour ne rendre que le YYY-MM-DD de l’horodatage UTC complet, utilisez la méthode JavaScript slice pour supprimer l’horodatage :

src/layouts/ExampleLayout.astro
---
const { frontmatter } = Astro.props;
---
<h1>{frontmatter.title}</h1>
<p>{frontmatter.pubDate.toString().slice(0,10)}</p>

Pour voir un exemple d’utilisation de toLocaleDateString pour formater le jour, le mois et l’année à la place, voir le [<FormattedDate /> component] (https://github.com/withastro/astro/blob/latest/examples/blog/src/components/FormattedDate.astro) dans le modèle officiel du blog Astro.