diff --git a/.changeset/light-ads-grin.md b/.changeset/light-ads-grin.md new file mode 100644 index 000000000..acd18e09e --- /dev/null +++ b/.changeset/light-ads-grin.md @@ -0,0 +1,5 @@ +--- +'astro': minor +--- + +Replace collections API with new file-based routing support diff --git a/docs/src/config.ts b/docs/src/config.ts index 3dea10f6c..01f30955a 100644 --- a/docs/src/config.ts +++ b/docs/src/config.ts @@ -12,13 +12,15 @@ export const SIDEBAR = { { text: 'Components', link: 'core-concepts/astro-components' }, { text: 'Pages', link: 'core-concepts/astro-pages' }, { text: 'Layouts', link: 'core-concepts/layouts' }, - { text: 'Collections', link: 'core-concepts/collections' }, + { text: 'Routing', link: 'core-concepts/routing' }, { text: 'Partial Hydration', link: 'core-concepts/component-hydration' }, { text: 'Guides', header: true }, { text: 'Styling & CSS', link: 'guides/styling' }, - { text: 'Data Fetching', link: 'guides/data-fetching' }, { text: 'Markdown', link: 'guides/markdown-content' }, + { text: 'Data Fetching', link: 'guides/data-fetching' }, + { text: 'Pagination', link: 'guides/pagination' }, + { text: 'RSS', link: 'guides/rss' }, { text: 'Supported Imports', link: 'guides/imports' }, { text: 'Aliases', link: 'guides/aliases' }, { text: 'Deploy a Website', link: 'guides/deploy' }, diff --git a/docs/src/pages/core-concepts/collections.md b/docs/src/pages/core-concepts/collections.md deleted file mode 100644 index ef209e8a4..000000000 --- a/docs/src/pages/core-concepts/collections.md +++ /dev/null @@ -1,292 +0,0 @@ ---- -layout: ~/layouts/MainLayout.astro -title: Collections ---- - -**Collections** are a special type of [page](/core-concepts/astro-pages) in Astro that can generate multiple pages at different URLs for a larger set of data. If you've seen an Astro file that starts with a dollar sign (ex: `$posts.astro`), that's a collection. - -Example use-cases include: - -- Generating multiple pages from remote data -- Generating multiple pages from local data (ex: list all markdown posts) -- pagination: `/posts/1`, `/posts/2`, etc. -- Grouping items into multiple pages: `/author/fred`, `/author/matthew`, etc. -- Generating one page per item: `/pokemon/pikachu`, `/pokemon/charmander`, etc. - -**Use a Collection when you need to generate multiple pages from a single template.** If you just want to generate a single page -- like a long list linking to every post on your blog -- then you can just use a normal [page](/core-concepts/astro-pages). - -## Using Collections - -To create a new Astro Collection, you need to do two things: - -### 1. Create the File - -Create a new file in the `src/pages` directory that starts with the dollar sign (`$`) symbol. This symbol is required to enable the Collections API. - -Astro uses file-based routing, which means that the file must match the URL that you expect to generate. You are able to define a custom route structure in the next step, but the collection file name must always match the start of the URL. - -- **Example**: `src/pages/$tags.astro` -> `/tags/:tag` -- **Example**: `src/pages/$posts.astro` -> `/posts/1`, `/posts/2`, etc. - -### 2. Export createCollection - -Every collection must define and export a `createCollection` function inside the component script. This exported function is where you fetch your data for the entire collection and tell Astro the exact URLs that you'd like to generate. It **MUST** be named `createCollection` and it must be exported. Check out the examples below for examples of how this should be implemented. - -```astro ---- -export async function createCollection() { - /* fetch collection data here */ - return { /* see examples below */ }; -} ---- - -``` - -API Reference: [createCollection](/reference/api-reference#collections-api) - -## Example: Individual Pages - -One of the most common reasons to use a collection is to generate a page for every item fetched from a larger dataset. In this example, we'll query a remote API and use the result to generate 150 different pages: one for each pokemon returned by the API call. - -Run this example in development, and then visit [http://localhost:3000/pokemon/pikachu](http://localhost:3000/pokemon/pikachu) to see one of the generated pages. - -```jsx ---- -// Example: src/pages/$pokemon.astro -// Define a `createCollection` function. -// In this example, we'll create a new page for every single pokemon. -export async function createCollection() { - // Do your data fetching here. - const allPokemonResponse = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`); - const allPokemonResult = await allPokemonResponse.json(); - const allPokemon = allPokemonResult.results; - return { - // `route` defines the URL structure for your collection. - // You can use any URL path pattern here, as long as it - // matches the filename prefix (`$pokemon.astro` -> `/pokemon/*`). - route: `/pokemon/:name`, - // `paths` tells Astro which pages to generate in your collection. - // Provide an array of `params` objects that match the `route` pattern. - paths() { - return allPokemon.map((pokemon, i) => ({params: {name: pokemon.name}})); - }, - // For each individual page, return the data needed on each page. - // If you needed to fetch more data for each page, you can do that here as well. - // Luckily, we loaded all of the data that we need at the top of the function. - async props({ params }) { - return {item: allPokemon.find((pokemon) => pokemon.name === params.name)}; - }, - }; -} -// The rest of your component script now runs on each individual page. -// "item" is one of the props returned in the `props()` function. -const {item} = Astro.props; ---- - - - Pokemon: {item.name}</head> - <body> - Who's that pokemon? It's {item.name}! - </body> -</html> -``` - -## Example: Grouping Content by Page - -You can also group items by page. In this example, we'll fetch data from the same Pokemon API. But instead of generating 150 pages, we'll just generate one page for every letter of the alphabet, creating an alphabetical index of Pokemon. - -_Note: Looking for pagination? Collections have built-in support to make pagination easy. Be sure to check out the next example._ - -```jsx ---- -// Define a `createCollection` function. -export async function createCollection() { - // Do your data fetching here. - const allPokemonResponse = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`); - const allPokemonResult = await allPokemonResponse.json(); - const allPokemon = allPokemonResult.results; - 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 { - // `route` defines the URL structure for your collection. - // You can use any URL path pattern here, as long as it - // matches the filename prefix (`$pokemon.astro` -> `/pokemon/*`). - route: `/pokemon/:letter`, - // `paths` tells Astro which pages to generate in your collection. - // Provide an array of `params` objects that match the `route` pattern. - // Here, we create a route for each letter (ex: "a" -> {letter: "a"}). - paths() { - return allLetters.map(letter => ({params: {letter}})); - }, - // `props` returns the data needed on each page. - // For each individual page, return the data needed on each page. - // If you needed to fetch more data for each page, you can do that here as well. - // Luckily, we loaded all of the data that we need at the top of the function. - async props({ params }) { - return { - letter: params.letter, - items: allPokemon.filter((pokemon) => pokemon.name[0] === params.letter)}; - }, - }; -} -// The rest of your component script now runs on each individual page. -// "item" is one of the props returned in the `props()` function. -const {letter, items} = Astro.props; ---- -<html lang="en"> - <head> - <title>Page: {letter}</head> - <body> - {items.map((pokemon) => (<h1>{pokemon.name}</h1>))} - </body> -</html> -``` - -## Example: Pagination - -Pagination is a common use-case for static websites. Astro has built-in pagination support that was designed to make pagination effortless. Just pass `paginate: true` in the `createCollection` return object to enable automatic pagination. - -This example provides a basic implementation of pagination. In the previous examples, we had fetched from a remote API. In this example, we'll fetch our local markdown files to create a paginated list of all posts for a blog. - -```jsx ---- -// Define a `createCollection` function. -export async function createCollection() { - const allPosts = Astro.fetchContent('../posts/*.md') // fetch local posts... - .sort((a, b) => a.title.localeCompare(b.title)); // ... and sort by title. - - return { - // Set "paginate" to true to enable pagination. - paginate: true, - // Remember to add the ":page?" param for pagination. - // The "?" indicates an optional param, since the first page does not use it. - // Example: `/posts`, `/posts/2`, `/posts/3`, etc. - route: '/posts/:page?', - // `paths()` - not needed if `:page?` is your only route param. - // If you define have other params in your route, then you will still - // need a paths() function similar to the examples above. - // - // `props()` - notice the new `{paginate}` argument! This is passed to - // the props() function when `paginate` is set to true. We can now use - // it to enable pagination on a certain prop. In this example, we paginate - // "posts" so that multiple pages will be generated based on the given page size. - async props({paginate}) { - return { - posts: paginate(allPosts, {pageSize: 10}), - }; - }, - }; -} -// Now, you can get the paginated posts from your props. -// Note that a paginated prop is a custom object format, where the data -// for the page is available at `posts.data`. See the next example to -// learn how to use the other properties of this object. -const {posts} = Astro.props; ---- -<html lang="en"> - <head> - <title>Pagination Example - - - {posts.data.map((post) => ( -

{post.title}

- - Read Post - ))} - - -``` - -## Example: Pagination Metadata - -Building on the example above: when you use the `paginate` API you get access to several other properties in the paginated data prop. Your paginated prop includes important metadata -for the collection, such as: `.page` for keeping track of your page number and `.url` for linking to other pages in the collection. - -In this example, we'll use these values to add pagination UI controls to your HTML template. - -```jsx ---- -export async function createCollection() { /* See Previous Example */ } -// Remember that a paginated prop uses a custom object format to help with pagination. -const {posts} = Astro.props; ---- - - - Pagination Example: Page Number {posts.page.current} - - - - - -
-
Results {posts.start + 1}–{posts.end + 1} of {posts.total}
-
- - - -``` - -## RSS Feeds - -You can generate an RSS 2.0 feed from the `createCollection()` result by adding the `rss` option. Here are all the options: - -```jsx -export async function createCollection() { - return { - paginate: true, - route: '/posts/:page?', - async props({ paginate }) { - /* Not shown: see examples above */ - }, - rss: { - title: 'My RSS Feed', - // if you want a full text feed, add your markup here (e.g. item.astro.html) - description: 'Description of the feed', - // (optional) add xmlns:* properties to root element - xmlns: { - itunes: 'http://www.itunes.com/dtds/podcast-1.0.dtd', - content: 'http://purl.org/rss/1.0/modules/content/', - }, - // (optional) add arbitrary XML to - customData: `en-us -The Sunset Explorers`, - // Format each paginated item in the collection - item: (item) => ({ - title: item.title, - description: item.description, - // enforce GMT timezone (otherwise it'll be different based on where it's built) - pubDate: item.pubDate + 'Z', - // link is required, shows up in RSS readers - link: '/collection/' + item.id - // (optional) custom data is supported here as well - customData: ``${Astro.site}collection/${item.id}`` - }), - }, - }; -} -``` - -Astro will generate your RSS feed at the URL `/feed/[collection].xml`. For example, `/src/pages/$podcast.astro` would generate URL `/feed/podcast.xml`. - -Even though Astro will create the RSS feed for you, you'll still need to add `` tags manually in your `` HTML for feed readers and browsers to pick up: - -```html - -``` - -### 📚 Further Reading - -- [Fetching data in Astro](/guides/data-fetching) -- API Reference: [createCollection()](/reference/api-reference#createcollection) -- API Reference: [createCollection() > Pagination](/reference/api-reference#pagination) -- API Reference: [createCollection() > RSS](/reference/api-reference#rss) diff --git a/docs/src/pages/core-concepts/routing.md b/docs/src/pages/core-concepts/routing.md new file mode 100644 index 000000000..712a2fba8 --- /dev/null +++ b/docs/src/pages/core-concepts/routing.md @@ -0,0 +1,103 @@ +--- +layout: ~/layouts/MainLayout.astro +title: Routing +--- + +Astro uses **file-based routing** to generate your build URLs based on the file layout of your project `src/pages` directory. When a file is added to the `src/pages` directory of your project, it is automatically available as a route based on its filename. + +## Static routes + +Astro Components (`.astro`) and Markdown Files (`.md`) in the `src/pages` directory become pages on your website. Each page's route is decided based on it's filename and path within the `src/pages` directory. This means that there is no separate "routing config" to maintain in an Astro project. + +```bash +# Example: Static routes +src/pages/index.astro -> mysite.com/ +src/pages/about.astro -> mysite.com/about +src/pages/about/index.astro -> mysite.com/about +src/pages/about/me.astro -> mysite.com/about/me +src/pages/posts/1.md -> mysite.com/posts/1 +``` + +## Dynamic routes + +Sometimes, you need to generate many URLs from a single page component. Astro uses file-based routing to support **dynamic route parameters** in the filename, so that one page can match many dynamic routes based on some pattern. + +An important thing to keep in mind: Astro is a static site builder. There is no Astro server to run in production, which means that every page must be built ahead of time. Pages that use dynamic routes must export a `getStaticPaths()` function which will tell Astro exactly what pages to generate. Learn more by viewing the complete [API Reference](/reference/api-reference#getstaticpaths). + + + +### Named parameters + +Dynamic parameters are encoded into the filename using `[bracket]` notation: + +- `pages/blog/[slug].astro` → `/blog/:slug` (`/blog/hello-world`, `/blog/post-2`, etc.) +- `pages/[username]/settings.astro` → (`/fred/settings`, `/drew/settings`, etc.) +- `pages/[lang]-[version]/info.astro` → (`/en-v1/info`, `/fr-v2/info`, etc.) + +#### Example: Named parameters + +Consider the following page `pages/post/[pid].astro`: + +```jsx +--- +// Example: src/pages/post/[pid].astro +const {pid} = Astro.request.params; +--- +

Post: {pid}

+``` + +Any route like `/post/1`, `/post/abc`, etc. will be matched by `pages/post/[pid].astro`. The matched path parameter will be passed to the page component at `Astro.request.params`. + +For example, the route `/post/abc` will have the following `Astro.request.params` object available: + +```json +{ "pid": "abc" } +``` + +Multiple dynamic route segments can be combined to work the same way. The page `pages/post/[pid]/[comment].astro` will match the route `/post/abc/a-comment` and its `query` object will be: + +```json +{ "pid": "abc", "comment": "a-comment" } +``` + +### Rest parameters + +If you need more flexibility in your URL routing, you can use a rest parameter as a universal catch-all. You do this by adding three dots (`...`) inside your brackets. For example: + +- `pages/post/[...slug].astro` → (`/post/a`, `/post/a/b`, `/post/a/b/c`, etc.) + +Matched parameters will be sent as a query parameter (`slug` in the example) to the page. In the example above, the path `/post/a/b/c` will have the following `query` object: + +```json +{ "slug": "a/b/c" } +``` + +You can use names other than `slug`, such as: `[...param]` or `[...name]`. + +Rest parameters are optional by default, so `pages/post/[...slug].astro` could match `/post/` as well. + +#### Example: Rest parameters + +For a real-world example, you might implement GitHub's file viewer like so: + +``` +/[org]/[repo]/tree/[branch]/[...file] +``` + +In this example, a request for `/snowpackjs/astro/tree/main/docs/public/favicon.svg` would result in the following parameters being available to the page: + +```js +{ + org: 'snowpackjs', + repo: 'astro', + branch: 'main', + file: 'docs/public/favicon.svg' +} +``` + +## Caveats + +- Static routes without path params will take precedence over all other routes, and named path params over catch all path params. Take a look at the following examples: + - `pages/post/create.astro` - Will match `/post/create` + - `pages/post/[pid].astro` - Will match `/post/1`, `/post/abc`, etc. But not `/post/create` + - `pages/post/[...slug].astro` - Will match `/post/1/2`, `/post/a/b/c`, etc. But not `/post/create`, `/post/abc` diff --git a/docs/src/pages/foo/index.astro b/docs/src/pages/foo/index.astro new file mode 100644 index 000000000..653debaa8 --- /dev/null +++ b/docs/src/pages/foo/index.astro @@ -0,0 +1 @@ +

hello

\ No newline at end of file diff --git a/docs/src/pages/guides/pagination.md b/docs/src/pages/guides/pagination.md new file mode 100644 index 000000000..bcce12e6b --- /dev/null +++ b/docs/src/pages/guides/pagination.md @@ -0,0 +1,108 @@ +--- +layout: ~/layouts/MainLayout.astro +title: Pagination +--- + +Astro supports built-in, automatic pagination for large collections of data that need to be split into multiple pages. Astro also automatically includes pagination metadata for things like previous/next page URL, total number of pages, and more. + +## When to use pagination + +Pagination is only useful when you need to generate multiple, numbered pages from a larger data set. + +If all of your data can fit on a single page then you should consider using a static [page component](/core-concepts/astro-pages) instead. + +If you need to split your data into multiple pages but do not want those page URLs to be numbered, then you should use a [dynamic page](/core-concepts/routing) instead without pagination (Example: `/tag/[tag].astro`). + +## How to use pagination + +### Create your page component + +To automatically paginate some data, you'll first need to create your page component. This is the component `.astro` file that every page in the paginated collection will inherit from. + +Pagination is built on top of dynamic page routing, with the page number in the URL represented as a dynamic route param: `[page].astro` or `[...page].astro`. If you aren't familiar with routing in Astro, quickly familiarize yourself with our [Routing documentation](/core-concepts/routing) before continuing. + +Your first page URL will be different depending on which type of query param you use: + +- `/posts/[page].astro` will generate the URLs `/posts/1`, `/posts/2`, `/posts/3`, etc. +- `/posts/[...page].astro` will generate the URLs `/posts`, `/posts/2`, `/posts/3`, etc. + +### calling the `paginate()` function + +Once you have decided on the file name/path for your page component, you'll need to export a [`getStaticPaths()`](/reference/api-reference#getstaticpaths) function from the component. `getStaticPaths()` is where you tell Astro what pages to generate. + +`getStaticPaths()` provides the `paginate()` function that we'll use to paginate your data. In the example below, we'll use `paginate()` to split a list of 150 Pokemon into 15 pages of 10 Pokemon each. + +```js +export async function getStaticPaths({ paginate }) { + // Load your data with fetch(), Astro.fetchContent(), etc. + const response = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`); + const result = await response.json(); + const allPokemon = result.results; + // Return a paginated collection of paths for all posts + return paginate(allPokemon, { pageSize: 10 }); +} +// If set up correctly, The page prop now has everything that +// you need to render a single page (see next section). +const { page } = Astro.props; +``` + +`paginate()` generates the correct array of path objects for `getStaticPaths()`. This automatically tells Astro to create a new URL for every page of the collection. The page data will then be passed as a `page` prop to the `.astro` page component. + +### using the `page` prop + +Once you've set up your page component and defined your `getStaticPaths()` function, you're ready to design your page template. Each page in the paginated collection will be passed its data in the `page` prop. + +```astro +--- +export async function getStaticPaths { /* ... */ } +const { page } = Astro.props; +--- +

Page {page.currentPage}

+ +``` + +The `page` prop has several useful properties, but the most important one is `page.data`. This is the array containing the page's slice of data that you passed to the `paginate()` function. For example, if you called `paginate()` on an array of 150 Pokemon: + +- `/1`: `page.data` would be an array of the first 10 Pokemon +- `/2`: `page.data` would be an array of Pokemon 11-20 +- `/3`: `page.data` would be an array of Pokemon 21-30 +- etc. etc. + +The `page` prop includes other helpful metadata, like `page.url.next`, `page.url.prev`, `page.total`, and more. See our [API reference](/reference/api-reference#the-pagination-page-prop) for the full `page` interface. + + +## Nested pagination + +A more advanced use-case for pagination is **nested pagination.** This is when pagination is combined with other dynamic route params. You can use nested pagination to group your paginated collection by some property or tag. + +For example, if you want to group your paginated markdown posts by some tag, you would use nested pagination by creating a `/src/pages/[tag]/[page].astro` page that would match the following URLS: + +- `/red/1` (tag=red) +- `/red/2` (tag=red) +- `/blue/1` (tag=blue) +- `/green/1` (tag=green) + +Nested pagination works by returning an array of `paginate()` results from `getStaticPaths()`, one for each grouping. In the following example, we will implement nested pagination to build the URLs listed above: + +```js +--- +// Example: /src/pages/[tag]/[page].astro +export function getStaticPaths({paginate}) { + const allTags = ['red', 'blue', 'green']; + const allPosts = Astro.fetchContent('../../posts/*.md'); + // For every tag, return a paginate() result. + // Make sure that you pass `{params: {tag}}` to `paginate()` + // so that Astro knows which tag grouping the result is for. + return allTags.map((tag) => { + const filteredPosts = allPosts.filter((post) => post.tag === tag); + return paginate(filteredPosts, { + params: { tag }, + pageSize: 10 + }); + }); +} +const { page } = Astro.props; +const { params } = Astro.request; +``` diff --git a/docs/src/pages/guides/rss.md b/docs/src/pages/guides/rss.md new file mode 100644 index 000000000..d4ac14d5f --- /dev/null +++ b/docs/src/pages/guides/rss.md @@ -0,0 +1,42 @@ +--- +layout: ~/layouts/MainLayout.astro +title: RSS +--- + +Astro supports fast, automatic RSS feed generation for blogs and other content websites. + +You can create an RSS feed from any Astro page that uses a `getStaticPaths()` function for routing. Only dynamic routes can use `getStaticPaths()` today (see [Routing](/core-concepts/routing). + +> We hope to make this feature available to all other pages before v1.0. As a workaround, you can convert a static route to a dynamic route that only generates a single page. See [Routing](/core-concepts/routing) for more information about dynamic routes. + +Create an RSS Feed by calling the `rss()` function that is passed as an argument to `getStaticPaths()`. This will create an `rss.xml` file in your final build based on the data that you provide using the `items` array. + +```js +// Example: /src/pages/posts/[...page].astro +// Place this function inside your Astro component script. +export async function getStaticPaths({rss}) { + const allPosts = Astro.fetchContent('../post/*.md'); + const sortedPosts = allPosts.sort((a, b) => new Date(b.date) - new Date(a.date)); + // Generate an RSS feed from this collection + rss({ + // The RSS Feed title, description, and custom metadata. + title: 'Don’s Blog', + description: 'An example blog on Astro', + customData: `en-us`, + // The list of items for your RSS feed, sorted. + items: sortedPosts.map(item => ({ + title: item.title, + description: item.description, + link: item.url, + pubDate: item.date, + })), + // Optional: Customize where the file is written to. + // Otherwise, defaults to "/rss.xml" + dest: "/my/custom/feed.xml", + }); + // Return your paths + return [...]; +} +``` + +Note: RSS feeds will **not** be built during development. Currently, RSS feeds are only generated during your final build. diff --git a/docs/src/pages/reference/api-reference.md b/docs/src/pages/reference/api-reference.md index 720df458f..c4c214363 100644 --- a/docs/src/pages/reference/api-reference.md +++ b/docs/src/pages/reference/api-reference.md @@ -64,92 +64,152 @@ const data = Astro.fetchContent('../pages/post/*.md'); // returns an array of po `Astro.site` returns a `URL` made from `buildOptions.site` in your Astro config. If undefined, this will return a URL generated from `localhost`. -## Collections API +## `getStaticPaths()` -A collection is any file in the `src/pages` directory that starts with a dollar sign (`$`) and includes an exported `createCollection` function in the component script. +If a page uses dynamic params in the filename, that component will need to export a `getStaticPaths()` function. -Check out our [Astro Collections](/core-concepts/collections) guide for more information and examples. - -### `createCollection()` +This function is required because Astro is a static site builder. That means that your entire site is built ahead of time. If Astro doesn't know to generate a page at build time, your users won't see it when they visit your site. ```jsx --- -export async function createCollection() { - return { /* ... */ }; +export async function getStaticPaths() { + return [ + { params: { ... } }, + { params: { ... } }, + { params: { ... } }, + // ... + ]; } --- ``` -⚠️ The `createCollection()` function executes in its own isolated scope before page loads. Therefore you can't reference anything from its parent scope, other than file imports. The compiler will warn if you break this requirement. +The `getStaticPaths()` function should return an array of objects to determine which paths will be pre-rendered by Astro. -The `createCollection()` function should returns an object of the following shape: +⚠️ The `getStaticPaths()` function executes in its own isolated scope once, before any page loads. Therefore you can't reference anything from its parent scope, other than file imports. The compiler will warn if you break this requirement. -| Name | Type | Description | -| :--------- | :--------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `route` | `string` | **Required.** A route pattern for matching URL requests. This is used to generate the page URL in your final build. It must begin with the file name: for example, `pages/$tags.astro` must return a `route` that starts with `/tags/`. | -| `paths` | `{params: Params}[]` | Return an array of all URL to be generated. | -| `props` | `async ({ params, paginate }) => object` | **Required.** Load data for the page that will get passed to the page component as props. | -| `paginate` | `boolean` | Optional: Enable automatic pagination. See next section. | -| `rss` | [RSS](/reference/api-reference#rss-feed) | Optional: generate an RSS 2.0 feed from this collection ([docs](/reference/api-reference#rss-feed)) | +### `params` -### Pagination +The `params` key of every returned object tells Astro what routes to build. The returned params must map back to the dynamic parameters and rest parameters defined in your component filepath. -Enable pagination for a collection by returning `paginate: true` from `createCollection()`. This passes a `paginate()` argument to `props()` that you can use to return paginated data in your HTML template via props. +`params` are encoded into the URL, so only strings are supported as values. The value for each `params` object must match the parameters used in the page name. -The `paginate()` function that you use inside of `props()` has the following interface: +For example, suppose that you have a page at `src/pages/posts/[id].astro`. If you export `getStaticPaths` from this page and return the following for paths: -```ts -/* the "paginate()" passed to props({paginate}) */ -type PaginateFunction = ( - data: any[], - args?: { - /* pageSize: set the number of items to be shown on every page. Defaults to 10. */ - pageSize?: number; - } -) => PaginatedCollectionResult; +```js +--- +export async function getStaticPaths() { + return [ + { params: { id: '1' } }, + { params: { id: '2' } } + ]; +} +const {id} = Astro.request.params; +--- +

{id}

+``` -/* the paginated return value, aka the prop passed to every page in the collection. */ -interface PaginatedCollectionResult { - /** result */ - data: any[]; +Then Astro will statically generate `posts/1` and `posts/2` at build time. - /** metadata */ - /** the count of the first item on the page, starting from 0 */ - start: number; - /** the count of the last item on the page, starting from 0 */ - end: number; - /** total number of results */ - total: number; - page: { - /** the current page number, starting from 1 */ - current: number; - /** number of items per page (default: 25) */ - size: number; - /** number of last page */ - last: number; - }; - url: { - /** url of the current page */ - current: string; - /** url of the previous page (if there is one) */ - prev: string | undefined; - /** url of the next page (if there is one) */ - next: string | undefined; - }; +### Data Passing with `props` + +To pass additional data to each generated page, you can also set a `props` value on every returned path object. Unlike `params`, `props` are not encoded into the URL and so aren't limited to only strings. + +For example, suppose that you generate pages based off of data fetched from a remote API. You can pass the full data object to the page component inside of `getStaticPaths`: + +```js +--- +export async function getStaticPaths() { + const data = await fetch('...').then(response => response.json()); + return data.map((post) => { + return { + params: { id: post.id }, + props: { post } }; + }); +} +const {id} = Astro.request.params; +const {post} = Astro.props; +--- +

{id}: {post.name}

+``` + +Then Astro will statically generate `posts/1` and `posts/2` at build time using the page component in `pages/posts/[id].astro`. The page can reference this data using `Astro.props`: + +### `paginate()` + +Pagination is a common use-case for websites that Astro natively supports via the `paginate()` function. `paginate()` will automatically generate the array to return from `getStaticPaths()` that creates one URL for every page of the paginated collection. The page number will be passed as a param, and the page data will be passed as a `page` prop. + +```js +export async function getStaticPaths({ paginate }) { + // Load your data with fetch(), Astro.fetchContent(), etc. + const response = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=150`); + const result = await response.json(); + const allPokemon = result.results; + // Return a paginated collection of paths for all posts + return paginate(allPokemon, { pageSize: 10 }); +} +// If set up correctly, The page prop now has everything that +// you need to render a single page (see next section). +const { page } = Astro.props; +``` + +`paginate()` assumes a file name of `[page].astro` or `[...page].astro`. The `page` param becomes the page number in your URL: + +- `/posts/[page].astro` would generate the URLs `/posts/1`, `/posts/2`, `/posts/3`, etc. +- `/posts/[...page].astro` would generate the URLs `/posts`, `/posts/2`, `/posts/3`, etc. + +#### The pagination `page` prop + +Pagination will pass a `page` prop to every rendered page that represents a single page of data in the paginated collection. This includes the data that you've paginated (`page.data`) as well as metadata for the page (`page.url`, `page.start`, `page.end`, `page.total`, etc). This metadata is useful for for things like a "Next Page" button or a "Showing 1-10 of 100" message. + +| Name | Type | Description | +| :----------------- | :-------------------: | :-------------------------------------------------------------------------------------------------------------------------------- | +| `page.data` | `Array` | Array of data returned from `data()` for the current page. | +| `page.start` | `number` | Index of first item on current page, starting at `0` (e.g. if `pageSize: 25`, this would be `0` on page 1, `25` on page 2, etc.). | +| `page.end` | `number` | Index of last item on current page. | +| `page.size` | `number` | How many items per-page. | +| `page.total` | `number` | The total number of items across all pages. | +| `page.currentPage` | `number` | The current page number, starting with `1`. | +| `page.lastPage` | `number` | The total number of pages. | +| `page.url.current` | `string` | Get the URL of the current page (useful for canonical URLs) | +| `page.url.prev` | `string \| undefined` | Get the URL of the previous page (will be `undefined` if on page 1). | +| `page.url.next` | `string \| undefined` | Get the URL of the next page (will be `undefined` if no more pages). | + +### `rss()` + +RSS feeds are another common use-case that Astro supports natively. Call the `rss()` function to generate an `/rss.xml` feed for your project using the same data that you loaded for this page. This file location can be customized (see below). + +```js +// Example: /src/pages/posts/[...page].astro +// Place this function inside your Astro component script. +export async function getStaticPaths({rss}) { + const allPosts = Astro.fetchContent('../post/*.md'); + const sortedPosts = allPosts.sort((a, b) => new Date(b.date) - new Date(a.date)); + // Generate an RSS feed from this collection + rss({ + // The RSS Feed title, description, and custom metadata. + title: 'Don’s Blog', + description: 'An example blog on Astro', + customData: `en-us`, + // The list of items for your RSS feed, sorted. + items: sortedPosts.map(item => ({ + title: item.title, + description: item.description, + link: item.url, + pubDate: item.date, + })), + // Optional: Customize where the file is written to. + // Defaults to "/rss.xml" + dest: "/my/custom/feed.xml", + }); + // Return a paginated collection of paths for all posts + return [...]; } ``` -📚 Learn more about pagination (and see an example) in our [Astro Collections guide.](/core-concepts/collections). - -### RSS - -Create an RSS 2.0 feed for a collection by returning `paginate: true` & an `rss` object from `createCollection()`. The `rss` object will be used to generate the contents of the RSS XML file. - -The `rss` object follows the `CollectionRSS`data type: - ```ts -export interface CollectionRSS { +// The full type definition for the rss() function argument: +interface RSSArgument { /** (required) Title of the RSS Feed */ title: string; /** (required) Description of the RSS Feed */ @@ -158,8 +218,14 @@ export interface CollectionRSS { xmlns?: Record; /** Specify custom data in opening of file */ customData?: string; + /** + * Specify where the RSS xml file should be written. + * Relative to final build directory. Example: '/foo/bar.xml' + * Defaults to '/rss.xml'. + */ + dest?: string; /** Return data about each item */ - item: (item: T) => { + items: { /** (required) Title of item */ title: string; /** (required) Link to item */ @@ -170,12 +236,10 @@ export interface CollectionRSS { description?: string; /** Append some other XML-valid data to this item */ customData?: string; - }; + }[]; } ``` -📚 Learn more about RSS feed generation (and see an example) in our [Astro Collections guide.](/core-concepts/collections). - ## `import.meta` > In this section we use `[dot]` to mean `.`. This is because of a bug in our build engine that is rewriting `import[dot]meta[dot]env` if we use `.` instead of `[dot]`. diff --git a/examples/blog-multiple-authors/astro.config.mjs b/examples/blog-multiple-authors/astro.config.mjs index 2c8e9130e..88a052f97 100644 --- a/examples/blog-multiple-authors/astro.config.mjs +++ b/examples/blog-multiple-authors/astro.config.mjs @@ -4,7 +4,7 @@ export default { // dist: './dist', // When running `astro build`, path to final static output // public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that don’t need processing. buildOptions: { - // site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. + site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. sitemap: true, // Generate sitemap (set to "false" to disable) }, devOptions: { diff --git a/examples/blog-multiple-authors/src/components/Nav.astro b/examples/blog-multiple-authors/src/components/Nav.astro index a7ef0985f..04a537f87 100644 --- a/examples/blog-multiple-authors/src/components/Nav.astro +++ b/examples/blog-multiple-authors/src/components/Nav.astro @@ -56,8 +56,8 @@ a { diff --git a/examples/blog-multiple-authors/src/components/PostPreview.astro b/examples/blog-multiple-authors/src/components/PostPreview.astro index b126ca2fb..19e1362e5 100644 --- a/examples/blog-multiple-authors/src/components/PostPreview.astro +++ b/examples/blog-multiple-authors/src/components/PostPreview.astro @@ -56,7 +56,7 @@ time {

{post.title}

- {author.name} + {author.name}

{post.description} diff --git a/examples/blog-multiple-authors/src/pages/$author.astro b/examples/blog-multiple-authors/src/pages/$author.astro deleted file mode 100644 index 76b372897..000000000 --- a/examples/blog-multiple-authors/src/pages/$author.astro +++ /dev/null @@ -1,105 +0,0 @@ ---- -import MainHead from '../components/MainHead.astro'; -import Nav from '../components/Nav.astro'; -import PostPreview from '../components/PostPreview.astro'; -import Pagination from '../components/Pagination.astro'; - -// page -let title = 'Don’s Blog'; -let description = 'An example blog on Astro'; -let canonicalURL = Astro.request.canonicalURL; - -// collection -import authorData from '../data/authors.json'; - -export function createCollection() { - /** Load posts */ - let allPosts = Astro.fetchContent('./post/*.md'); - return { - paginate: true, - route: `/author/:author/:page?`, - paths() { - let allAuthorsUnique = [...new Set(allPosts.map(p => p.author))]; - return allAuthorsUnique.map(author => ({params: {author}})) - }, - async props({ params, paginate }) { - /** filter posts by author, sort by date */ - const filteredPosts = allPosts - .filter((post) => post.author === params.author) - .sort((a, b) => new Date(b.date) - new Date(a.date)); - return { - posts: paginate(filteredPosts, {pageSize: 1}), - } - }, - }; -} - - -const { posts } = Astro.props; -const { params } = Astro.request; -const author = authorData[posts.data[0].author]; ---- - - - - {title} - - - - - - -