Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content Collections guide #2141

Merged
merged 127 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from 117 commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
d9546c9
new: content collections intro
bholmesdev Dec 5, 2022
a06e06f
draft: paste from RFC initial pass
bholmesdev Dec 5, 2022
d991b50
new: finalize feature walkthrough, add landing pg example
bholmesdev Dec 5, 2022
b03445b
feat: add Rendering Content docs
bholmesdev Dec 5, 2022
55db443
feat: add to nav
bholmesdev Dec 6, 2022
1c57b9c
edit: nested directory and schema
bholmesdev Dec 6, 2022
93bc3a2
new: zod quick reference
bholmesdev Dec 6, 2022
292bc1d
edit: quick reference
bholmesdev Dec 6, 2022
dd70ad2
edit: better intro
bholmesdev Dec 6, 2022
70d7bf9
edit: better Zod onboarding callouts
bholmesdev Dec 6, 2022
ac9e57e
edit: clarify why rendering content is better than body
bholmesdev Dec 6, 2022
936233e
new: Rendering guide for pages
bholmesdev Dec 6, 2022
11a2901
edit: highlight collection names
bholmesdev Dec 6, 2022
3f70af5
edit: remove unneeded optional comment
bholmesdev Dec 6, 2022
7c56330
edit: medium-to-large -> non-trivial
bholmesdev Dec 7, 2022
dd34936
srcDir -> src
bholmesdev Dec 7, 2022
6a8418a
edit: refine glossary
bholmesdev Dec 7, 2022
a393479
edit: tweak CMS and database comparison
bholmesdev Dec 7, 2022
33d0162
adding -> defining a collection
bholmesdev Dec 7, 2022
854ea85
edit: render example lead-in
bholmesdev Dec 7, 2022
64a0d23
edit: injectedFrontmatter destructure clarity
bholmesdev Dec 7, 2022
235b0d7
edit: add filename to render entry example
bholmesdev Dec 7, 2022
b734578
edit: tweak readingTime wording
bholmesdev Dec 7, 2022
67f4eef
edit: simplify schema example
bholmesdev Dec 7, 2022
0ba8392
edit: refine collection-with-dashes ex
bholmesdev Dec 7, 2022
1be56c7
Frontmatter YAML -> Markdown and MDX
bholmesdev Dec 7, 2022
e807849
edit: apparently my dashes aren't cool enough
bholmesdev Dec 7, 2022
14bf265
edit: wording on getCollection types
bholmesdev Dec 7, 2022
650e382
edit: blog -> src/content/blog
bholmesdev Dec 7, 2022
6e04f2e
edit: better directory return type explanation
bholmesdev Dec 7, 2022
3fd4103
nit: indents
bholmesdev Dec 7, 2022
f281d84
edit: subdirectory lead-in
bholmesdev Dec 7, 2022
509934a
edit: en -> en/
bholmesdev Dec 7, 2022
b7d60bf
edit: better file tree
bholmesdev Dec 8, 2022
cd3d071
edit: remove dup file name
bholmesdev Dec 8, 2022
dc56c5c
edit: better filetree on collections example
bholmesdev Dec 8, 2022
98cf092
edit: filetree for docs schema
bholmesdev Dec 8, 2022
6fafee3
edit: refine dynamic route section
bholmesdev Dec 8, 2022
43a2b71
edit: there's always a hippo watching over us
bholmesdev Dec 9, 2022
aa5b091
edit: more context on props -> renderEntry
bholmesdev Dec 9, 2022
8d31687
edit: pass entry as prop key
bholmesdev Dec 9, 2022
bcb3567
edit: getting from -> querying
bholmesdev Dec 9, 2022
194795f
nit: empty line before list
bholmesdev Dec 9, 2022
67052a6
nit: trailing newline
bholmesdev Dec 9, 2022
b493c8a
edit: move type example into other example
bholmesdev Dec 9, 2022
01819fe
edit: getting -> querying
bholmesdev Dec 9, 2022
c8bb5da
edit: Markdown TM
bholmesdev Dec 9, 2022
a53bab5
edit: Sarah lead-in edits
bholmesdev Dec 9, 2022
5b55a4f
edit: better schema config explainer
bholmesdev Dec 9, 2022
11fec3c
edit: remove renderEntry individual args ex
bholmesdev Dec 9, 2022
e8934a7
edit: tweak return type for review
bholmesdev Dec 9, 2022
148fe9a
edit: fine I'll remove the lead-in I spent 15 min debating
bholmesdev Dec 9, 2022
e71465c
edit: org nested dir redraft
bholmesdev Dec 9, 2022
497fcf0
Merge branch 'new/content-schemas-guide' of github.com:withastro/docs…
bholmesdev Dec 9, 2022
5a3f4e1
edit: without a glossary or emojis I'm a shallow husk of a docs writer
bholmesdev Dec 9, 2022
8b5f3a9
edit: work glossary into content dir section
bholmesdev Dec 9, 2022
cc7abca
edit: truncate dashes col name example
bholmesdev Dec 9, 2022
1b92643
edit: break out collection functions to headings
bholmesdev Dec 9, 2022
a2d657c
nit: example -> eg
bholmesdev Dec 9, 2022
634df16
edit: refine generating pages example
bholmesdev Dec 9, 2022
56333d3
edit: redraft why zod sections
bholmesdev Dec 9, 2022
16e94cd
edit: reorder `defineCollection` instructions
bholmesdev Dec 9, 2022
de6de68
edit: return type -> data returned example
bholmesdev Dec 9, 2022
066092f
edit: remove landing page example at end
bholmesdev Dec 9, 2022
3149f64
edit: redraft Collections
bholmesdev Dec 9, 2022
b2bfc83
edit: add homepage preview example to rendering content
bholmesdev Dec 9, 2022
4e43f02
edit: remove dead querying nested dirs link
bholmesdev Dec 9, 2022
e495936
edit: remove redundant link
bholmesdev Dec 9, 2022
e929b03
edit: object keys -> collection names
bholmesdev Dec 9, 2022
957dbf2
fix: broken code comment
bholmesdev Dec 9, 2022
74ebdf8
edit: landing page ex tweak
bholmesdev Dec 9, 2022
5d9c06f
edit: 2 -> two
bholmesdev Dec 10, 2022
6e25027
edit: rogue space
bholmesdev Dec 10, 2022
43c63d4
edit: no fancy apostrophes
bholmesdev Dec 10, 2022
556228c
edit: no fancy apostrophes 2
bholmesdev Dec 10, 2022
649bd0b
edit: include Zod for better SEO
bholmesdev Dec 10, 2022
2159de0
edit: reserved directory -> special
bholmesdev Dec 13, 2022
f60eca1
edit: remove `data`
bholmesdev Dec 13, 2022
083269e
edit: refine collections lead-in
bholmesdev Dec 13, 2022
57f39ee
nit: `renderEntry()`
bholmesdev Dec 13, 2022
20215c4
edit: have a growing number...
bholmesdev Dec 13, 2022
32b98d1
edit: new but also not new collection lead-in
bholmesdev Dec 13, 2022
77c09b3
edit: undead URLs are pages right?
bholmesdev Dec 13, 2022
fecca4c
edit: that's a wee bit bri'ish innit
bholmesdev Dec 13, 2022
c446fe7
edit: remove superfluous file tree
bholmesdev Dec 13, 2022
228b208
edit: trailing /, newline
bholmesdev Dec 13, 2022
0cbce48
edit: clarify schema config
bholmesdev Dec 13, 2022
ebc2f60
edit: well aren't you detail orien`tag`
bholmesdev Dec 13, 2022
6f91765
edit: refine frontmatter prop x data type
bholmesdev Dec 13, 2022
7fae5bd
edit: refine zod schema intro
bholmesdev Dec 13, 2022
5d973ae
edit: whitespace in collection example
bholmesdev Dec 13, 2022
1c4b102
edit: CHRIS? IS THAT AN EMOJI? I'M CALLING THE POLICE 🚔
bholmesdev Dec 13, 2022
b7e3b65
edit: clarify with uncompiled
bholmesdev Dec 13, 2022
ec65b65
edit: refine landing page example (now usage ex)
bholmesdev Dec 13, 2022
dacc5a4
edit: remove unused `tags`
bholmesdev Dec 13, 2022
a59a39a
edit: move "querying nested directories" to `getCollection` docs
bholmesdev Dec 13, 2022
a42ef74
edit: regexcellent catch
bholmesdev Dec 13, 2022
4afbde0
edit: usage example l3 -> l4
bholmesdev Dec 13, 2022
5fc82de
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
9f82c52
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
2fede08
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
e2a4956
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
868ab93
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
b265911
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
3c54672
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
80991fe
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
c26d987
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
cca41b0
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
b70c786
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
84af2d2
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
36f9d68
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
9bd3c71
Update src/pages/en/guides/content-collections.md
bholmesdev Dec 14, 2022
94c53c7
edit: rendering-post-contents rework
bholmesdev Dec 14, 2022
79fb7c5
Extend extensions
delucis Dec 14, 2022
14987a5
2 green bullet points sitting on a wall…
delucis Dec 14, 2022
e05e9f3
new: add experimental note
bholmesdev Dec 15, 2022
dcb7dce
edit: renderEntry -> render
bholmesdev Dec 15, 2022
ee70831
edit: a -> your wording
bholmesdev Dec 16, 2022
0cd4f0f
Spaces
delucis Dec 16, 2022
61c0281
Unmangle before rework
delucis Dec 16, 2022
e9bac98
Rework “rendering content” section
delucis Dec 16, 2022
834adfd
Fix broken fragment link
delucis Dec 16, 2022
b1fbb2f
add parentheses to functions
sarah11918 Dec 16, 2022
8ab0037
strictNullChecks
sarah11918 Dec 16, 2022
adf6e20
Merge branch 'main' into new/content-schemas-guide
delucis Dec 16, 2022
d0ca82c
Use `<FileTree>` :christmas_tree:
delucis Dec 16, 2022
7d7f235
Add Since component
delucis Dec 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/i18n/en/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export default [
key: 'guides/server-side-rendering',
},
{ text: 'Authoring Content', slug: 'guides/content', key: 'guides/content' },
{
text: 'Content Collections (Experimental)',
slug: 'guides/content-collections',
key: 'guides/content-collections',
},
{ text: 'Connecting a CMS', slug: 'guides/cms', key: 'guides/cms' },
{ text: 'Images', slug: 'guides/images', key: 'guides/images' },
{ text: 'Fonts', slug: 'guides/fonts', key: 'guides/fonts' },
Expand Down
379 changes: 379 additions & 0 deletions src/pages/en/guides/content-collections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,379 @@
---
layout: ~/layouts/MainLayout.astro
title: Content Collections (Experimental)
description: Content collections help organize your Markdown and type-check your frontmatter with schemas.
i18nReady: false
---

Content collections help organize your Markdown or MDX and type-check your frontmatter with schemas. Collections may be helpful if you:

- **Plan to use Markdown content in multiple areas** of your site (landing pages, footers, navigation, etc).
- **Want Astro to enforce frontmatter fields,** and fail if fields are missing (e.g. every blog post should have a title and description).

## Getting started

Content Collections are experimental. To enable this feature, set the `experimental.contentCollections` flag in your Astro config:

```js
// astro.config.mjs
import { defineConfig } from 'astro';

export default defineConfig({
experimental: {
contentCollections: true,
},
});
```

sarah11918 marked this conversation as resolved.
Show resolved Hide resolved
## The content directory

Astro treats the `src/content/` directory as special. This is where **collections** (folders) of Markdown/MDX **entries** (files) can be stored, with a single configuration file to define each collection's **schema** (frontmatter data types and shape). Files other than your `.md`/`.mdx` content are not permitted inside `src/content/`.

## Collections

A collection is a directory in `src/content/` containing Markdown or MDX fields. Every Markdown or MDX file in `src/content/` **must** belong to a collection directory, since Astro [provides built-in functions](#querying-content-collections) for querying your content by the collection directory name.

Content within a collection should share the same frontmatter shape and types. You can optionally enforce these types [by configuring a schema](/en/guides/content-collections/#defining-a-collection-schema).

To create a collection, add a new directory to `src/content/`. Then, add Markdown or MDX entries that share frontmatter properties. The following example shows two collections: `blog` and `newsletter`.

```bash "newsletter/" "blog/"
src/content/
│ # All blog posts have the same frontmatter properties
├── blog/
│ ├── columbia.md
│ ├── endeavour.md
│ └── enterprise.md
│ # All newsletters have the same frontmatter properties
└── newsletter/
├── week-1.md
├── week-2.md
└── week-3.md
```

### Collections with nested directories

Collections are **top-level folders** within `src/content/`. You cannot nest collections, but you may use nested directories within a collection to better organize a collection's content. All nested directories will share the same schema defined for the top-level collection.

For example, you can use this structure for internationalization:

```bash
src/content/
└── docs/
│ # docs schema applies to all nested directories 👇
├── en/
├── es/
└── ...
```


## Defining a collection schema

Schemas are an optional way to enforce frontmatter types in a collection. Astro uses [Zod](https://github.com/colinhacks/zod) to validate your frontmatter with schemas in the form of [Zod objects](https://github.com/colinhacks/zod#objects).

To configure schemas, create a `src/content/config.ts` file (`.js` and `.mjs` extensions are also supported). This file should:

1. Import the `defineCollection` and `z` utilities from `astro:content`.
2. Define a `schema` for each collection.
2. Export a single `collections` object, with each object key corresponding to the collection's folder name.

For example, say you maintain two collections: one for release announcements and one for blog content. Your entries at `src/content/announcements` should include a `title` and `version`. Your `src/content/engineering-blog/` collection entries should have a `title`, list of `tags`, and an optional `image` URL.

You can specify each expected property in the `schema` field of `defineCollection`:

```ts
// src/content/config.ts
import { z, defineCollection } from 'astro:content';

const releases = defineCollection({
schema: {
title: z.string(),
version: z.number(),
},
});

const engineeringBlog = defineCollection({
schema: {
title: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
},
});

export const collections = {
releases: releases,
delucis marked this conversation as resolved.
Show resolved Hide resolved
// Don't forget 'quotes' for collection names containing dashes
'engineering-blog': engineeringBlog,
};
```

### Schema data types with Zod

Markdown and MDX frontmatter can contain booleans, strings, numbers, objects, and arrays. When defining a schema, you must include every frontmatter property along with its data type. To define and validate this schema, we use a library called [Zod](https://github.com/colinhacks/zod), which is available via the `z` import.

You can extend any of these types with `.optional()` if a frontmatter property is not always required or `.defaultValue(value)` to provide a value to use when the property is not set in frontmatter. If only a limited set of values is valid for a property, you can specify these using [the `.enum()` method](https://github.com/colinhacks/zod#zod-enums).

The following schema illustrates each of these data types in use:

```ts
import { z, defineCollection } from 'astro:content';

defineCollection({
schema: {
isDraft: z.boolean(),
title: z.string(),
sortOrder: z.number(),
image: z.object({
src: z.string(),
alt: z.string(),
}),
tags: z.array(z.string()), // An array of strings
footnote: z.string().optional(),
author: z.string().default('Anonymous'),
language: z.enum(['en', 'es']),
}
})
```

### Advanced schema features

You can use all of Zod’s properties and methods with content schemas. This includes transforming a frontmatter value into another value, checking the shape of string values with built-in regexes, and more.

```ts
{
// Allow only strings representing email addresses
authorContact: z.string().email(),
// Allow URL strings only (e.g. `https://example.com`)
canonicalURL: z.string().url(),
// Parse publishDate as a browser-standard `Date` object
publishDate: z.string().transform(str => new Date(str)),
}
```

📚 See [Zod’s documentation](https://github.com/colinhacks/zod) for a complete list of features.

## Querying content collections

Astro provides two functions to query collections:

### `getCollection()`

`getCollection()` returns multiple entries in a collection. It requires the name of a `collection` as a parameter. By default, it returns all items in the collection.

It can also take a second, optional parameter: a filter function based on schema properties. This allows you to query for only some items in a collection based on `id`, `slug`, or frontmatter values via the `data` object.

```astro
---
import { getCollection } from 'astro:content';

// Get all `src/content/blog/` entries
const allBlogPosts = await getCollection('blog');

// Only return posts with `draft: true` in the frontmatter
const draftBlogPosts = await getCollection('blog', ({ data }) => {
return data.draft === true;
});
---
```

#### Querying nested directories

The filter function can also be used to query for nested directories within a collection. Since the `id` includes the full nested path, you can filter by the start of each `id` to only return items from a specific nested directory:

```astro
---
import { getCollection } from 'astro:content';
const enDocs = await getCollection('docs', ({ id }) => {
// Return all entries in `src/content/docs/en/`
return id.startsWith('en/');
});
---
```

### `getEntry()`

`getEntry()` is function that returns a specific entry in a collection by entry ID (file path relative to the collection). Both of these are required parameters.

```astro
---
import { getEntry } from 'astro:content';

const enterprise = await getEntry('blog', 'enterprise.md');
---
```

### Data returned from a collection query

`getCollection()` and `getEntry()` will return entries that include:
- `id` - a unique ID using the file path relative to `src/content/[collection]`
- `slug` - a URL-ready slug. Defaults to the ID without the file extension.
- `data` - an object of frontmatter properties inferred from your collection schema. Defaults to `any` if no schema is configured.
- `body` - a string containing the raw, uncompiled body of the Markdown or MDX document.
- `render()` - a function that returns the compiled body of the Markdown or MDX document via the `<Content />` component ([See complete documentation](#render)).

Querying your content files with `getCollection()`or `getEntry()` allows you to use frontmatter properties from an entry's `data` object in [JSX-like expressions](/en/core-concepts/astro-components/#jsx-like-expressions) or pass props to other components, such as a layout. You can optionally add type safety with a built-in utility.

For example, you can use a `getCollection()` query to filter and then display a list of links to all your published blog posts:
```astro
---
// src/pages/index.astro
import { getCollection } from 'astro:content';

// Get all published blog posts
const blogPosts = await getCollection('blog', ({ data }) => {
return data.status === 'published';
});
---
<ul>
{allBlogPosts.map(post => (
<li>
<a href={post.data.slug}>{post.data.title}</a>
<time datetime={post.data.publishedDate.toISOString()}>
{post.data.publishedDate.toDateString()}
</time>
</li>
))}
</ul>
```

### Collection entry types

If a page or component uses content from a `getCollection()` or `getEntry()` query, you can use the `CollectionEntry` utility to type its props:

```astro /CollectionEntry([<.+>])?/
---
// src/components/BlogCard.astro
import type { CollectionEntry } from 'astro:content';

interface Props {
// Get type of a `blog` collection entry
post: CollectionEntry<'blog'>;
}

// `post.data` will match your collection schema
const { post } = Astro.props;
---
```

bholmesdev marked this conversation as resolved.
Show resolved Hide resolved
## Rendering page content from entries

To render the content of your Markdown and MDX entries, use the `<Content />` component returned by the `render()` function. This allows you to generate pages from your content entries (see [Generating pages from content collections](#generating-pages-from-content-collections)), add post previews to your homepage, or display your content elsewhere on your site.
bholmesdev marked this conversation as resolved.
Show resolved Hide resolved
sarah11918 marked this conversation as resolved.
Show resolved Hide resolved

### `render()`

`render()` is an extension function every content collection entry that returns a `<Content />` component. This contains the rendered content of the Markdown or MDX file.
delucis marked this conversation as resolved.
Show resolved Hide resolved

For example, this page renders the contents of `content/announcements/welcome.md` and uses some of its frontmatter properties:

```astro "render()"
---
// src/pages/welcome-announcement.astro
import Layout from '../../layouts/Layout.astro';
import { getEntry } from 'astro:content';
const announcementPost = await getEntry('announcements', 'welcome.md');
const { Content } = await announcementPost.render();
---
<Layout>
<h1>{announcementPost.data.title}</h1>
<p>Written by: {announcementPost.data.author}</p>
<Content />
</Layout>
```

### Access content headings from `render()`

Astro [generates a list of headings](/en/guides/markdown-content/#exported-properties) for Markdown and MDX documents. You can access this list using the `headings` property from `render()`:

```astro "{ headings }"
---
import { getCollection } from 'astro:content';
const blogPosts = await getCollection('blog');
---

{blogPosts.map(async (post) => {
const { headings } = await post.render();
const h1 = headings.find(h => h.depth === 1);
return <p>{h1}</p>
})}
```

### Access injected frontmatter from `render()`

Astro allows you to [inject frontmatter using remark or rehype plugins.](/en/guides/markdown-content/#example-injecting-frontmatter) You can access these values using the `injectedFrontmatter` property from `render()`:

```astro "{ injectedFrontmatter }"
---
import { getCollection } from 'astro:content';
const blogPosts = await getCollection('blog');
---

{blogPosts.map(async (post) => {
const { injectedFrontmatter } = await post.render();
return <p>{post.data.title} — {injectedFrontmatter.readingTime}</p>
})}
```

Assuming `readingTime` was injected ([see our reading time example](/en/guides/markdown-content/#example-calculate-reading-time)), it will be available on the `injectedFrontmatter` object.

<details>
<summary>**🙋 Why don't `getCollection` and `getEntry` contain these values?**</summary>
bholmesdev marked this conversation as resolved.
Show resolved Hide resolved
sarah11918 marked this conversation as resolved.
Show resolved Hide resolved

The remark and rehype pipelines are only run when your content is **rendered.** This lets `render()` access anything generated by these plugins like injected frontmatter. To stay performant, `getCollection()` and `getEntry()` do not have this capability.

</details>

## Generating pages from content collections

You can create pages based on your content collections using [dynamic routes](/en/core-concepts/routing/#dynamic-routes).

Use `getCollection()` inside a [`getStaticPaths()`](/en/reference/api-reference/#getstaticpaths) function to query your content entries and provide the `slug` parameter for each page.

For example, you can dynamically create a page for each entry in a `blog` collection, including nested directories, by creating a `.astro` page with a [rest parameter in its filename](/en/core-concepts/routing/#rest-parameters) to match file paths of any depth.

```astro "{ slug: entry.slug }"
---
// src/pages/posts/[...slug].astro
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
const blog = await getCollection('blog');
return blog.map(entry => ({
params: { slug: entry.slug },
}));
}
---
```

This will generate page routes for every entry in the `blog` collection, mapping each entry’s slug to a URL. For example, an entry at `src/content/blog/hello-world.md` will have a slug of `hello-world` and the collection entry `src/content/blog/en/intro.md` will have a slug of `en/intro`.

Because this dynamic route is in `src/pages/posts/`, the final URLs will be `/posts/hello-world/` and `/posts/en/intro/`.

### Rendering post contents

When you pass each page route an entry via `props` in your `getStaticPaths()` function, you have access to the entry from `Astro.props`. You can use its frontmatter values via the `data` object and use `render()` to render its content via a `<Content /> component. You can optionally add type safety using the `CollectionEntry` utility.

```astro "render()" "props: entry"
---
// src/pages/blog/[...slug].astro
import { getCollection, CollectionEntry } from 'astro:content';

export async function getStaticPaths() {
const docs = await getCollection('docs');
return docs.map(entry => ({
// Pass blog entry via props
params: { slug: entry.slug, props: { entry } },
}));
}

interface Props {
// Optionally use `CollectionEntry` for type safety
entry: CollectionEntry<'docs'>;
}

const { entry } = Astro.props;
const { Content } = await entry.render();
---

<h1>{entry.data.title}</h1>
<Content />
```