2021-04-20 23:15:47 +00:00
# 🍱 Collections
2021-06-15 00:21:00 +00:00
## What are Collections?
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
Astro Collections help you break up a larger set of data into multiple pages. Examples of use-cases include:
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
- Pagination: `/posts/1` , `/posts/2` , etc.
- Grouping content by author: `/author/fred` , `/author/matthew` , etc.
- Grouping content by some tag: `/tags/red` , `/tags/blue` , etc.
- Working with remote data
- Mixing remote and local data
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
**When to use Collections: When you need to reuse a single template to generate multiple pages from a larger dataset.** If you just want to generate a single page (ex: a long list of every post on your site) then you can just fetch that data on a normal Astro page without using the Collection API.
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
## Collections API
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
To create a new Astro Collection, you must do three things:
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
1. Create a new file in the `src/pages` directory that starts with the `$` symbol. This is required to enable the Collections API.
2021-06-15 00:22:00 +00:00
- Example: `src/pages/$posts.astro` -> `/posts/1` , `/posts/2` , etc.
- Example: `src/pages/$tags.astro` -> `/tags/:tag` (or `/tags/:tag/1` )
2021-06-15 00:21:00 +00:00
2. Define and export the `collection` prop: `collection.data` is how you'll access the data for every page in the collection. Astro populates this prop for you automatically. It MUST be named `collection` and it must be exported.
2021-06-15 00:22:00 +00:00
- Example: `export let collection;`
2021-06-15 00:21:00 +00:00
3. Define and export `createCollection` function: this tells Astro how to load and structure your collection data. Check out the examples below for documentation on how it should be implemented. It MUST be named `createCollection` and it must be exported.
2021-04-20 23:15:47 +00:00
2021-06-15 00:22:00 +00:00
- Example: `export async function createCollection() { /* ... */ }`
- API Reference: [createCollection][collection-api]
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
## Example: Simple Pagination
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
```jsx
---
// Define the `collection` prop.
2021-04-20 23:15:47 +00:00
export let collection: any;
2021-06-15 00:21:00 +00:00
// Define a `createCollection` function.
2021-04-20 23:15:47 +00:00
export async function createCollection() {
2021-06-15 00:21:00 +00:00
const allPosts = Astro.fetchContent('../posts/*.md'); // fetch local posts.
allPosts.sort((a, b) => a.title.localeCompare(b.title)); // sort by title.
2021-04-20 23:15:47 +00:00
return {
2021-06-15 00:21:00 +00:00
// Because you are not doing anything more than simple pagination,
// its fine to just return the full set of posts for the collection data.
async data() { return allPosts; },
// number of posts loaded per page (default: 25)
2021-06-15 00:22:00 +00:00
pageSize: 10,
2021-04-20 23:15:47 +00:00
};
}
---
2021-06-15 00:21:00 +00:00
< html lang = "en" >
< head >
2021-06-15 00:22:00 +00:00
< title > Pagination Example: Page Number {collection.page.current}< / title >
2021-06-15 00:21:00 +00:00
< / head >
< body >
{collection.data.map((post) => (
< h1 > {post.title}< / h1 >
< time > {formatDate(post.published_at)}< / time >
< a href = {post.url} > Read Post< / a >
))}
< / body >
< / html >
2021-04-20 23:15:47 +00:00
```
2021-06-15 00:21:00 +00:00
## Example: Pagination Metadata
2021-04-20 23:15:47 +00:00
```jsx
---
2021-06-15 00:22:00 +00:00
// In addition to `collection.data` usage illustrated above, the `collection`
// prop also provides some important metadata for you to use, like: `collection.page` ,
2021-06-15 00:21:00 +00:00
// `collection.url` , `collection.start` , `collection.end` , and `collection.total` .
// In this example, we'll use these values to do pagination in the template.
2021-04-20 23:15:47 +00:00
export let collection: any;
2021-06-15 00:21:00 +00:00
export async function createCollection() { /* See Previous Example */ }
2021-04-20 23:15:47 +00:00
---
< html lang = "en" >
< head >
2021-06-15 00:22:00 +00:00
< title > Pagination Example: Page Number {collection.page.current}< / title >
2021-04-20 23:15:47 +00:00
< link rel = "canonical" href = {collection.url.current} / >
< link rel = "prev" href = {collection.url.prev} / >
< link rel = "next" href = {collection.url.next} / >
< / head >
< body >
< main >
2021-05-24 20:53:39 +00:00
< h5 > Results {collection.start + 1}– {collection.end + 1} of {collection.total}< / h5 >
2021-04-20 23:15:47 +00:00
{collection.data.map((post) => (
< h1 > {post.title}< / h1 >
< time > {formatDate(post.published_at)}< / time >
2021-06-15 00:21:00 +00:00
< a href = {post.url} > Read Post< / a >
))}
2021-04-20 23:15:47 +00:00
< / main >
< footer >
< h4 > Page {collection.page.current} / {collection.page.last}< / h4 >
< nav class = "nav" >
< a class = "prev" href = {collection.url.prev | | ' # ' } > Prev< / a >
< a class = "next" href = {collection.url.next | | ' # ' } > Next< / a >
< / nav >
< / footer >
< / body >
< / html >
```
2021-06-15 00:21:00 +00:00
## Example: Grouping Content by Tag, Author, etc.
```jsx
---
// Define the `collection` prop.
export let collection: any;
2021-04-20 23:15:47 +00:00
2021-06-15 00:22:00 +00:00
// Define a `createCollection` function.
2021-06-15 00:21:00 +00:00
// In this example, we'll customize the URLs that we generate to
2021-06-15 00:22:00 +00:00
// create a new page to group every pokemon by first letter of their name.
2021-06-15 00:21:00 +00:00
export async function createCollection() {
const allPokemonResponse = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`);
const allPokemonResult = await allPokemonResponse.json();
2021-06-17 18:47:48 +00:00
const allPokemon = allPokemonResult.results;
2021-06-15 00:21:00 +00:00
const allLetters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
return {
// `routes` defines the total collection of routes as `params` data objects.
// In this example, we format each letter (ex: "a") to params (ex: {letter: "a"}).
routes: allLetters.map(letter => {
const params = {letter};
return params;
}),
// `permalink` defines the final URL for each route object defined in `routes` .
// It should always match the file location (ex: `src/pages/$pokemon.astro` ).
permalink: ({ params }) => `/pokemon/${params.letter}` ,
// `data` is now responsible for return the data for each page.
2021-06-15 00:22:00 +00:00
// Luckily we had already loaded all of the data at the top of the function,
// so we just filter the data here to group pages by first letter.
2021-06-15 00:21:00 +00:00
// If you needed to fetch more data for each page, you can do that here as well.
async data({ params }) {
return allPokemon.filter((pokemon) => pokemon.name[0] === params.letter);
},
// Finally, `pageSize` and `pagination` is still on by default. Because
// we don't want to paginate the already-grouped pages a second time, we'll
// disable pagination.
2021-06-15 00:22:00 +00:00
pageSize: Infinity,
2021-06-15 00:21:00 +00:00
};
}
---
< html lang = "en" >
< head >
< title > Pokemon: {collection.params.letter}< / head >
< body >
{collection.data.map((pokemon) => (< h1 > {pokemon.name}< / h1 > ))}
< / body >
< / html >
```
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
## Example: Individual Pages from a Collection
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
```jsx
---
// Define the `collection` prop.
export let collection: any;
2021-04-20 23:15:47 +00:00
2021-06-15 00:22:00 +00:00
// Define a `createCollection` function.
2021-06-15 00:21:00 +00:00
// In this example, we'll create a new page for every single pokemon.
export async function createCollection() {
const allPokemonResponse = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`);
const allPokemonResult = await allPokemonResponse.json();
const allPokemon = allPokemonResult.result;
return {
// `routes` defines the total collection of routes as data objects.
routes: allPokemon.map((pokemon, i) => {
const params = {name: pokemon.name, index: i};
return params;
}),
// `permalink` defines the final URL for each route object defined in `routes` .
permalink: ({ params }) => `/pokemon/${params.name}` ,
// `data` is now responsible for return the data for each page.
2021-06-15 00:22:00 +00:00
// Luckily we had already loaded all of the data at the top of the function,
// so we just filter the data here to group pages by first letter.
2021-06-15 00:21:00 +00:00
// If you needed to fetch more data for each page, you can do that here as well.
async data({ params }) {
return allPokemon[params.index];
},
2021-06-15 00:22:00 +00:00
// Note: The default pageSize is fine because technically only one data object
2021-06-15 00:21:00 +00:00
// is ever returned per route. We set it to Infinity in this example for completeness.
2021-06-15 00:22:00 +00:00
pageSize: Infinity,
2021-06-15 00:21:00 +00:00
};
}
---
< html lang = "en" >
< head >
< title > Pokemon: {collection.params.name}< / head >
< body >
Who's that pokemon? It's {collection.data.name}!
< / body >
< / html >
```
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
## Tips
2021-04-20 23:15:47 +00:00
2021-06-15 00:21:00 +00:00
- If you find yourself duplicating markup across many pages and collections, you should probably be using more reusable components.
2021-04-20 23:15:47 +00:00
### 📚 Further Reading
- [Fetching data in Astro][docs-data]
- API Reference: [collection][collection-api]
- API Reference: [createCollection()][create-collection-api]
2021-04-23 16:44:41 +00:00
- API Reference: [Creating an RSS feed][create-collection-api]
2021-04-20 23:15:47 +00:00
[docs-data]: ../README.md#-fetching-data
[collection-api]: ./api.md#collection
[create-collection-api]: ./api.md#createcollection
[example-blog]: ../examples/blog
[fetch-content]: ./api.md#fetchcontent