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; ---- - -
-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 @@ +{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]; ---- - - -
-