diff --git a/docs/docs/adding-tags-and-categories-to-blog-posts.md b/docs/docs/adding-tags-and-categories-to-blog-posts.md index 4ca91d3f91a51..733f0853443ee 100644 --- a/docs/docs/adding-tags-and-categories-to-blog-posts.md +++ b/docs/docs/adding-tags-and-categories-to-blog-posts.md @@ -6,14 +6,22 @@ Creating tag pages for your blog post is a way to let visitors browse related co To add tags to your blog posts, you will first want to have your site set up to turn your markdown pages into blog posts. To get your blog pages set up, see the [tutorial on Gatsby's data layer](/tutorial/part-four/) and [Adding Markdown Pages](/docs/adding-markdown-pages/). -## Add a tags field to your blog posts +The process will essentially look like this: + +1. Add tags to your `markdown` files +2. Write a query to get all tags for your posts +3. Make a tags page template (for `/tags/{tag}`) +4. Modify `gatsby-node.js` to render pages using that template +5. Make a tags index page (`/tags`) that renders a list of all tags +6. _(optional)_ Render tags inline with your blog posts + +## Add tags to your `markdown` files You add tags by defining them in the `frontmatter` of your Markdown file. The `frontmatter` is the area at the top surrounded by dashes that includes post data like the title and date. -``` +```md --- title: "A Trip To the Zoo" - --- I went to the zoo today. It was terrible. @@ -21,7 +29,7 @@ I went to the zoo today. It was terrible. Fields can be strings, numbers, or arrays. Since a post can usually have many tags, it makes sense to define it as an array. Here we add our new tags field: -``` +```md --- title: "A Trip To the Zoo" tags: ["animals", "Chicago", "zoos"] @@ -32,20 +40,22 @@ I went to the zoo today. It was terrible. If `gatsby develop` is running, restart it so Gatsby can pick up the new fields. -## Query your fields +## Write a query to get all tags for your posts Now these fields are available in the data layer. To use field data, query it using `graphql`. All fields are available to query inside `frontmatter` -Try running in Graph_i_QL the following query +Try running in GraphiQL (`localhost:8000/___graphql`) the following query ```graphql -query IndexQuery { - allMarkdownRemark { - totalCount +{ + allMarkdownRemark( + sort: { order: DESC, fields: [frontmatter___date] } + limit: 1000 + ) { edges { node { frontmatter { - title + path tags } } @@ -54,230 +64,238 @@ query IndexQuery { } ``` -The query fetches the title and tags for every blog post. +The resulting data includes the `path` and `tags` frontmatter for each post, which is all the data we'll need to create pages for each tag which contain a list of posts under that tag. Let's make the tag page template now: -Using this query, we can create a component for a blog front page that lists all posts and their tags. +## Make a tags page template (for `/tags/{tag}`) -```jsx -const IndexPage = ({ data }) => ( -
-

My Travel Blog

- {data.allMarkdownRemark.edges.map(({ node }) => ( -
-

- {node.frontmatter.title} - — {node.frontmatter.tags.join(`, `)} -

-
- ))} -
-); - -export const query = graphql` - query IndexQuery { - allMarkdownRemark { - totalCount - edges { - node { - id - frontmatter { - title - tags - } - } - } - } - } -`; -``` +If you followed the tutorial for [Adding Markdown Pages](/docs/adding-tags-and-categories-to-blog-posts/), then this process should sound familiar: we'll make a tag page template, then use it in `createPages` in `gatsby-node.js` to generate individual pages for the tags in our posts. -## Create tag pages +First, we'll add a tags template at `src/templates/tags.js`: -Tag pages list all the tags or all items with a certain tag and are a great tool for organizing content and making your site easy to browse. +```jsx +import React from 'react'; +import PropTypes from 'prop-types'; -First you will need a tag page component. In this example, we add a tags component in `src/templates/tags.js` which we'll use to create an index tags page at `/tags` and individual tag pages. +// Components +import Link from 'gatsby-link'; + +const Tags = ({ pathContext, data }) => { + const { tag } = pathContext; + const { edges, totalCount } = data.allMarkdownRemark; + const tagHeader = `${totalCount} post${totalCount === 1 ? '' : 's'} tagged with "${tag}"`; -```jsx -import React from "react"; -import GatsbyLink from "gatsby-link"; - -export default function Tags({ pathContext }) { - const { posts, post, tag } = pathContext; - if (tag) { - return ( -
-

- {post.length} post{post.length === 1 ? "" : "s"} tagged with {tag} -

- - All tags -
- ); - } return (
-

Tags

-
); -} -``` - -Now we'll instruct Gatsby to create the tag pages. In the site's `gatsby-node.js` file we'll call the the [`createPages`](/docs/node-apis/#createPages) API to make a page for every tag. - -First create a function called `createTagPages`: - -```javascript -const path = require("path"); - -const createTagPages = (createPage, edges) => { - // Tell it to use our tags template. - const tagTemplate = path.resolve(`src/templates/tags.js`); - // Create an empty object to store the posts. - const posts = {}; - console.log("creating posts"); +}; - // Loop through all nodes (our markdown posts) and add the tags to our post object. +Tags.propTypes = { + pathContext: PropTypes.shape({ + tag: PropTypes.string.isRequired, + }), + data: PropTypes.shape({ + allMarkdownRemark: PropTypes.shape({ + totalCount: PropTypes.number.isRequired, + edges: PropTypes.arrayOf( + PropTypes.shape({ + node: PropTypes.shape({ + frontmatter: PropTypes.shape({ + path: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + }), + }), + }).isRequired, + ), + }), + }), +}; - edges.forEach(({ node }) => { - if (node.frontmatter.tags) { - node.frontmatter.tags.forEach(tag => { - if (!posts[tag]) { - posts[tag] = []; +export default Tags; + +export const pageQuery = graphql` + query TagPage($tag: String) { + allMarkdownRemark( + limit: 2000 + sort: { fields: [frontmatter___date], order: DESC } + filter: { frontmatter: { tags: { in: [$tag] } } } + ) { + totalCount + edges { + node { + frontmatter { + title + path + } + } } - posts[tag].push(node); - }); + } } - }); +`; +``` - // Create the tags page with the list of tags from our posts object. - createPage({ - path: "/tags", - component: tagTemplate, - context: { - posts, - }, - }); +**Note**: `propTypes` are included in this example to help you ensure you're getting all the data you need in the component, and to help serve as a guide while destructuring / using those props. - // For each of the tags in the post object, create a tag page. - - Object.keys(posts).forEach(tagName => { - const post = posts[tagName]; - createPage({ - path: `/tags/${tagName}`, - component: tagTemplate, - context: { - posts, - post, - tag: tagName, - }, - }); - }); -}; -``` +## Modify `gatsby-node.js` to render pages using that template -Then in the `createPages` API function, query using `graphql` for your fields and use that to call the new `createTagPages` function. +Now we've got a template. Great! I'll assume you followed the tutorial for [Adding Markdown Pages](/docs/adding-tags-and-categories-to-blog-posts/) and provide a sample `createPages` that generates post pages as well as tag pages. In the site's `gatsby-node.js` file, include `lodash` (`const _ = require('lodash')`) and then make sure your [`createPages`](/docs/node-apis/#createPages) looks something like this: -```javascript -exports.createPages = ({ graphql, boundActionCreators }) => { +```js +exports.createPages = ({ boundActionCreators, graphql }) => { const { createPage } = boundActionCreators; - // add the tags to the query - return new Promise((resolve, reject) => { - graphql(` - { - allMarkdownRemark { - edges { - node { - fields { - slug - } - frontmatter { - tags - title - } + + const blogPostTemplate = path.resolve('src/templates/blog.js'); + const tagTemplate = path.resolve('src/templates/tags.js'); + + return graphql(` + { + allMarkdownRemark( + sort: { order: DESC, fields: [frontmatter___date] } + limit: 2000 + ) { + edges { + node { + frontmatter { + path + tags } } } } - `).then(result => { - console.log(result); - const posts = result.data.allMarkdownRemark.edges; - - // call createTagPages with the result of posts - createTagPages(createPage, posts); - - // this is the original code used to create the pages from markdown posts - result.data.allMarkdownRemark.edges.map(({ node }) => { - createPage({ - path: node.fields.slug, - component: path.resolve(`./src/templates/blog-post.js`), - context: { - slug: node.fields.slug, - }, - }); + } + `).then(result => { + if (result.errors) { + return Promise.reject(result.errors); + } + + const posts = result.data.allMarkdownRemark.edges; + + // Create post detail pages + posts.forEach(({ node }) => { + createPage({ + path: node.frontmatter.path, + component: blogPostTemplate, + }); + }); + + // Tag pages: + let tags = []; + // Iterate through each post, putting all found tags into `tags` + _.each(posts, edge => { + if (_.get(edge, 'node.frontmatter.tags')) { + tags = tags.concat(edge.node.frontmatter.tags); + } + }); + // Eliminate duplicate tags + tags = _.uniq(tags); + + // Make tag pages + tags.forEach(tag => { + createPage({ + path: `/tags/${_.kebabCase(tag)}/`, + component: tagTemplate, + context: { + tag, + }, }); - resolve(); }); }); }; ``` +Some notes: -## Adding Tags To Your Blog Front Page +* Our graphql query only looks for data we need to generate these pages. Anything else can be queried again later (and, if you notice, we do this above in the tags template for the post title). +* While making the tag pages, note that we pass `tag` through in the `context`. This is the value that gets used in the `TagPage` query to limit our search to only posts tagged with the tag in the URL. -The blog front page we created previously doesn't link to the tag pages. One way to add this is by creating a tag component at `src/components/tags.js` +## Make a tags index page (`/tags`) that renders a list of all tags -```jsx -import React from "react"; -import Link from "gatsby-link"; +Our `/tags` page will simply list out all tags, followed by the number of posts with that tag: -export default function Tags({ list = [] }) { - return ( - - ); -} -``` +```jsx +import React from 'react'; +import PropTypes from 'prop-types'; -We can now use this new component on the blog home page by passing in our tags from the data layer: +// Utilities +import kebabCase from 'lodash/kebabcase'; -```jsx -import React from "react"; -import Tags from "../components/tags"; +// Components +import Helmet from 'react-helmet'; +import Link from 'gatsby-link'; -const IndexPage = ({ data }) => ( +const TagsPage = ({ data: { allMarkdownRemark: { group }, site: { siteMetadata: { title }} } }) =>
-

My Travel Blog

- {data.allMarkdownRemark.edges.map(({ node }) => ( -
-

{node.frontmatter.title}

- -
- ))} + +
+

Tags

+
    + {group.map(tag => ( +
  • + + {tag.fieldValue} ({tag.totalCount}) + +
  • + ))} +
+
-); +; + +TagsPage.propTypes = { + data: PropTypes.shape({ + allMarkdownRemark: PropTypes.shape({ + group: PropTypes.arrayOf( + PropTypes.shape({ + fieldValue: PropTypes.string.isRequired, + totalCount: PropTypes.number.isRequired, + }).isRequired, + ), + }), + site: PropTypes.shape({ + siteMetadata: PropTypes.shape({ + title: PropTypes.string.isRequired, + }), + }), + }), +}; + +export default TagsPage; + +export const pageQuery = graphql` + query TagsQuery { + site { + siteMetadata { + title + } + } + allMarkdownRemark( + limit: 2000 + filter: { frontmatter: { published: { ne: false } } } + ) { + group(field: frontmatter___tags) { + fieldValue + totalCount + } + } + } +`; ``` + +## _(optional)_ Render tags inline with your blog posts + +The home stretch! Anywhere else you'd like to render your tags, simply add them to the `frontmatter` section of your `graphql` query and access them in your component like any other prop.