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

[tutorial] new tutorial extensions #4876

Merged
merged 41 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5788338
initial structure and content for 2 new tutorial add-ons
sarah11918 Sep 29, 2023
30b53fd
current sidebar idea
sarah11918 Sep 29, 2023
2026f6a
updated to prerequisites for cc
sarah11918 Sep 29, 2023
7e701b0
first draft - content collections tutorial
sarah11918 Sep 29, 2023
1da69e6
made it clear view transitions is placeholder content only
sarah11918 Sep 29, 2023
f79ae0d
Merge branch 'main' into more-tut
sarah11918 Sep 29, 2023
63e1a19
remove links referring to other page
sarah11918 Sep 30, 2023
53e7c98
link fix
sarah11918 Sep 30, 2023
ce88863
revert heading change in sidebar
sarah11918 Sep 30, 2023
f8bea26
reorder sections to create reusable structure for these guides
sarah11918 Sep 30, 2023
76fb84f
replicating mini tut page structure for view transitions
sarah11918 Sep 30, 2023
bca33e8
draft of intro view transitions
sarah11918 Sep 30, 2023
c243b49
typos
sarah11918 Sep 30, 2023
b6ac69f
basic instructions to get default view transitions
sarah11918 Sep 30, 2023
54b54c0
avoid sidebar highlighting problem
sarah11918 Oct 9, 2023
b4ff214
adds instructions for updating scripts with `astro:page-load`
sarah11918 Oct 10, 2023
7b8eb5f
update theme toggle example
sarah11918 Oct 10, 2023
6a7df36
add animation examples
sarah11918 Oct 10, 2023
c0be394
data-astro-reload example
sarah11918 Oct 10, 2023
9034c86
added conclusion
sarah11918 Oct 10, 2023
a6a386e
update link for new location of extensions
sarah11918 Oct 10, 2023
2d36196
add the astro page swap script example
sarah11918 Oct 10, 2023
e6808f9
fix link bc it's actually called astro after swap
sarah11918 Oct 10, 2023
d0fad9e
Merge branch 'main' into more-tut
sarah11918 Oct 10, 2023
03ed190
update page titles and descriptions to match each other
sarah11918 Oct 10, 2023
cbc1219
add missing word
sarah11918 Oct 12, 2023
7d581a5
fix JS style
sarah11918 Oct 12, 2023
c63e217
Apply suggestions from Talking and Doc'ing code review
sarah11918 Oct 12, 2023
e69368d
Merge branch 'main' into more-tut
sarah11918 Oct 13, 2023
9d6a13e
Merge branch 'main' into more-tut
sarah11918 Oct 13, 2023
a2b06ce
improve message on content collections page
sarah11918 Oct 13, 2023
e078418
Yan nits!
sarah11918 Oct 13, 2023
7e6c503
less dense!
sarah11918 Oct 13, 2023
38e578c
added some tutorial UI.. for fun!
sarah11918 Oct 13, 2023
3f1b8e9
fix link
sarah11918 Oct 13, 2023
933fba3
missing wordl
sarah11918 Oct 13, 2023
e6a2183
missing space
sarah11918 Oct 15, 2023
5030975
extra space!
sarah11918 Oct 15, 2023
771df72
updated links at end of build a blog tutorial; renamed folder
sarah11918 Oct 15, 2023
01a51b6
fix link after folder rename
sarah11918 Oct 15, 2023
87779ae
Merge branch 'main' into more-tut
sarah11918 Oct 15, 2023
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
326 changes: 326 additions & 0 deletions src/content/docs/en/extend/add-content-collections.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
---
title: Tutorial - Extend with Content Collections
description: >-
Convert the Build a Blog tutorial code from file-based routing to content collections.
i18nReady: true
---
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'
import FileTree from '~/components/FileTree.astro'
import Since from '~/components/Since.astro'
import TypeScriptSettingTabs from '~/components/tabs/TypeScriptSettingTabs.astro'
import RecipeLinks from "~/components/RecipeLinks.astro"


**Content collections** are a powerful way to manage groups of similar content, such as blog posts. Collections help to organize your documents, validate your YAML frontmatter, and provide automatic TypeScript type-safety for all of your content (even if you don't write any TypeScript yourself).

## Prerequisites

You will need **an existing Astro project with Markdown or MDX files in the `src/pages/` folder**.

This tutorial uses the [Build a Blog tutorial's finished project](https://github.com/withastro/blog-tutorial-demo) to demonstrate converting a blog to content collections. You can fork and use that codebase locally, or complete the tutorial in the browser by [editing the blog tutorial's code in StackBlitz](https://stackblitz.com/github/withastro/blog-tutorial-demo/tree/complete?file=src%2Fpages%2Findex.astro).

You can instead follow similar steps in your own Astro project, but you will need to adjust the instructions for your own codebase. We recommend using our sample project to complete this short tutorial first. Then, you can use what you have learned to work on your own codebase.
sarah11918 marked this conversation as resolved.
Show resolved Hide resolved

## Build a Blog Tutorial Code

In the Build a Blog introductory tutorial, you learned about Astro's [built-in file-based routing](/en/core-concepts/routing/#static-routes): any `.astro` or `.md` file anywhere within the `src/pages/` folder automatically became a page on your site.

To create your first blog post at `https://example.com/posts/post-1/`, you created a `/posts/` folder and added the file `post-1.md`. You then added a new Markdown file to this folder every time you wanted to add a new blog post to your site.


## Pages vs Collections

Even when using content collections, you will still use the `src/pages/` folder for individual pages, such as your About Me page. But, moving your blog posts to the special `src/content/` folder will allow you to use more powerful and performant APIs to generate your blog post index and display your individual blog posts.

At the same time, you'll receive better guidance and autocompletion in your code editor because you will have a **[schema](/en/guides/content-collections/#defining-a-collection-schema)** to define a common structure for each post that Astro will help you enforce. In your schema, you can specify when frontmatter properties are required, such as a description or an author, and which data type each property must be, such as a string or an array. The leads to catching many mistakes sooner, with descriptive error messages telling you exactly what the problem is.
sarah11918 marked this conversation as resolved.
Show resolved Hide resolved

Read more about [Astro's content collections](/en/guides/content-collections/) in our guide, or get started with the instructions below to convert a basic blog from `src/pages/posts/` to `src/content/posts/`.

## Extending the blog tutorial with content collections

The steps below show you how to extend the final product of the Build a Blog tutorial by creating a content collection for the blog posts.

### Upgrade dependencies

1. Upgrade to the latest version of Astro, and upgrade all integrations to their latest versions by running the following commands in your terminal:

<PackageManagerTabs>
<Fragment slot="npm">
```shell
# Upgrade to Astro v3.x
npm install astro@latest

# Example: upgrade the blog tutorial Preact integration
npm install @astrojs/preact@latest
```
</Fragment>
<Fragment slot="pnpm">
```shell
# Upgrade to Astro v3.x
pnpm install astro@latest

# Example: upgrade the blog tutorial Preact integration
pnpm install @astrojs/preact@latest
```
</Fragment>
<Fragment slot="yarn">
```shell
# Upgrade to Astro v3.x
yarn add astro@latest

# Example: upgrade the blog tutorial Preact integration
yarn add @astrojs/preact@latest
```
</Fragment>
</PackageManagerTabs>

:::tip
If you are using your own project, then be sure to update any dependencies you have installed. The example blog tutorial codebase only uses the Preact integration.
:::

2. The blog tutorial uses the `base` (least strict) TypeScript setting. In order to use content collections, you must [set up TypeScript](/en/guides/content-collections/#setting-up-typescript) for content collections either by using the `strict` or `strictest` setting, or by adding two options in `tsconfig.json`.

In order to use content collections without writing TypeScript in the rest of the blog tutorial example, add the following two TypeScript configuration options:

```json title="tsconfig.json" ins={5,6}
{
// Note: No change needed if you use "astro/tsconfigs/strict" or "astro/tsconfigs/strictest"
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"strictNullChecks": true,
"allowJs": true
}
}
```

### Create a collection for your blog posts

3. Create a new **collection** (folder) called `src/content/posts/`.

4. Move all your existing blog posts (`.md` files) from `src/pages/posts/` into this new collection.

5. Create a `src/content/config.ts` file to [define a schema](/en/guides/content-collections/#defining-a-collection-schema) for your `postsCollection`:

```ts title="src/content/config.ts"
// Import utilities from `astro:content`
import { z, defineCollection } from "astro:content";
// Define a `type` and `schema` for each collection
const postsCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
pubDate: z.date(),
description: z.string(),
author: z.string(),
image: z.object({
url: z.string(),
alt: z.string()
}),
tags: z.array(z.string())
})
});
// Export a single `collections` object to register your collection(s)
export const collections = {
posts: postsCollection,
};
```

### Generate pages from a collection

6. Create a page file called `src/pages/posts/[...slug].astro`. Your Markdown and MDX files no longer automatically become pages using Astro's file-based routing when they are inside a collection, so you must create a page responsible for generating each individual blog post.

7. Add the following code to [query your collection](/en/guides/content-collections/#querying-collections) to make each blog post's slug and page content available to each page it will generate:

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

sarah11918 marked this conversation as resolved.
Show resolved Hide resolved
export async function getStaticPaths() {
const blogEntries = await getCollection('posts');
return blogEntries.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}

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


8. Render your post `<Content />` within the layout for Markdown pages. This allows you to specify a common layout for all of your posts.

```astro title="src/pages/posts/[...slug].astro" ins={15-17}
---
import { getCollection } from 'astro:content';
import MarkdownPostLayout from '../../layouts/MarkdownPostLayout.astro';

export async function getStaticPaths() {
const blogEntries = await getCollection('posts');
return blogEntries.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}

const { entry } = Astro.props;
const { Content } = await entry.render();
---
<MarkdownPostLayout frontmatter={entry.data}>
<Content />
</MarkdownPostLayout>
```

9. Remove the `layout` definition in each individual post's frontmatter. Your content is now wrapped in a layout when rendered, and this property is no longer needed.

```md title="src/content/posts/post-1.md" del={2}
---
layout: ../../layouts/MarkdownPostLayout.astro
title: 'My First Blog Post'
pubDate: 2022-07-01
...
---
```

### Replace `Astro.glob()` with `getCollection()`

10. Anywhere you have a list of blog posts, like the tutorial's Blog page (`src/pages/blog.astro/`), you will need to replace `Astro.glob()` with [`getCollection()`](/en/reference/api-reference/#getcollection) as the way to fetch content and metadata from your Markdown files.

```astro title="src/pages/blog.astro" "post.data" "getCollection(\"posts\")" "/posts/${post.slug}/" del={7} ins={2,8}
---
import { getCollection } from "astro:content";
import BaseLayout from "../layouts/BaseLayout.astro";
import BlogPost from "../components/BlogPost.astro";

const pageTitle = "My Astro Learning Blog";
const allPosts = await Astro.glob("../pages/posts/*.md");
const allPosts = await getCollection("posts");
---

11. You will also need to update references to the data returned for each `post`. You will now find your frontmatter values on the `data` property of each object. Also, when using collections each `post` object will have a page `slug`, not a full URL.

```astro title="src/pages/blog.astro" "data" "/posts/$\{post.slug\}/" del={14} ins={15}
---
import { getCollection } from "astro:content";
import BaseLayout from "../layouts/BaseLayout.astro";
import BlogPost from "../components/BlogPost.astro";

const pageTitle = "My Astro Learning Blog";
const allPosts = await getCollection("posts");
---
<BaseLayout pageTitle={pageTitle}>
<p>This is where I will post about my journey learning Astro.</p>
<ul>
{
allPosts.map((post) => (
<BlogPost url={post.url} title={post.frontmatter.title} />)}
<BlogPost url={`/posts/${post.slug}/`} title={post.data.title} />
sarah11918 marked this conversation as resolved.
Show resolved Hide resolved
))
}
</ul>
</BaseLayout>
```

12. The tutorial blog project also dynamically generates a page for each tag using `src/pages/tags/[tag].astro` and displays a list of tags at `src/pages/tags/index.astro`.

Apply the same changes as above to these two files:

- fetch data about all your blog posts using `getCollection("posts")` instead of using `Astro.glob()`
- access all frontmatter values using `data` instead of `frontmatter`
- create a page URL by adding the post's `slug` to the `/posts/` path

The page that generates individual tag pages now becomes:

```astro title="src/pages/tags/[tag].astro" "post.data.tags" "getCollection(\"posts\")" "post.data.title" ins={2} "/posts/${post.slug}/"
---
import { getCollection } from "astro:content";
sarah11918 marked this conversation as resolved.
Show resolved Hide resolved
import BaseLayout from "../../layouts/BaseLayout.astro";
import BlogPost from "../../components/BlogPost.astro";

export async function getStaticPaths() {
const allPosts = await getCollection("posts");
const uniqueTags = [...new Set(allPosts.map((post) => post.data.tags).flat())];

return uniqueTags.map((tag) => {
const filteredPosts = allPosts.filter((post) =>
post.data.tags.includes(tag)
);
return {
params: { tag },
props: { posts: filteredPosts },
};
});
}

const { tag } = Astro.params;
const { posts } = Astro.props;
---

<BaseLayout pageTitle={tag}>
<p>Posts tagged with {tag}</p>
<ul>
{ posts.map((post) => <BlogPost url={`/posts/${post.slug}/`} title={post.data.title} />) }
</ul>
</BaseLayout>
```

The tag index page now becomes:

```astro title="src/pages/tags/index.astro" "post.data" "getCollection(\"posts\")" ins={2}
---
import { getCollection } from "astro:content";
sarah11918 marked this conversation as resolved.
Show resolved Hide resolved
import BaseLayout from "../../layouts/BaseLayout.astro";
const allPosts = await getCollection("posts");
const tags = [...new Set(allPosts.map((post) => post.data.tags).flat())];
const pageTitle = "Tag Index";
---
...
```

### Update any frontmatter values to match your schema

13. If necessary, update any frontmatter values throughout your project, such as in your layout, that do not match your collections schema.

In the blog tutorial example, `pubDate` was a string. Now, according to the schema that defines types for the post frontmatter, `pubDate` will be a `Date`
object.

To render the date in the blog post layout, convert it to a string:

```astro title="src/layouts/MarkdownPostLayout.astro" ins="toString()"
...
<BaseLayout pageTitle={frontmatter.title}>
<p>{frontmatter.pubDate.toString().slice(0,10)}</p>
<p><em>{frontmatter.description}</em></p>
<p>Written by: {frontmatter.author}</p>
<img src={frontmatter.image.url} width="300" alt={frontmatter.image.alt} />
...
```

### Update RSS function

14. Lastly, the tutorial blog project includes an RSS feed. This function must also use `getCollection()` to return information from your blog posts. You will then generate the RSS items using the `data` object returned.

```js title="src/pages/rss.xml.js" del={2,11} ins={3,6,12-17}
import rss from '@astrojs/rss';
import { pagesGlobToRssItems } from '@astrojs/rss';
import { getCollection } from 'astro:content';

export async function GET(context) {
const posts = await getCollection("posts");
return rss({
title: 'Astro Learner | Blog',
description: 'My journey learning Astro',
site: context.site,
items: await pagesGlobToRssItems(import.meta.glob('./**/*.md')),
sarah11918 marked this conversation as resolved.
Show resolved Hide resolved
items: posts.map((post) => ({
title: post.data.title,
pubDate: post.data.pubDate,
description: post.data.description,
link: `/posts/${post.slug}/`,
})),
customData: `<language>en-us</language>`,
})
}
```

For the full example of the blog tutorial using content collections, see the [Content Collections branch](https://github.com/withastro/blog-tutorial-demo/tree/content-collections) of the tutorial repo.
Loading
Loading