Skip to content

Commit 09cf036

Browse files
committed
Add tags to blog posts
1 parent f13960c commit 09cf036

10 files changed

Lines changed: 159 additions & 5 deletions

File tree

gatsby-node.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ async function makePostPages(graphql, glob, createPage, template) {
2222
node {
2323
frontmatter {
2424
path
25+
tags
2526
}
2627
}
2728
}
@@ -41,7 +42,33 @@ async function makePostPages(graphql, glob, createPage, template) {
4142
createPage({
4243
path: node.frontmatter.path,
4344
component: template,
44-
context: {}, // additional data can be passed via context
45+
context: {
46+
tags: node.frontmatter.tags || [],
47+
}, // additional data can be passed via context
48+
});
49+
});
50+
51+
const tagMap = {};
52+
53+
// Build a map of tag → array of post paths
54+
posts.forEach(({ node }) => {
55+
const tags = node.frontmatter.tags || [];
56+
tags.forEach(tag => {
57+
if (!tagMap[tag]) tagMap[tag] = [];
58+
tagMap[tag].push(node.frontmatter.path);
59+
});
60+
});
61+
62+
const tagTemplate = path.resolve(`src/templates/tag.js`);
63+
64+
Object.keys(tagMap).forEach(tag => {
65+
createPage({
66+
path: `/blog/tags/${tag.toLowerCase().replace(/[^a-z0-9]+/g, '-')}/`,
67+
component: tagTemplate,
68+
context: {
69+
tag,
70+
posts: tagMap[tag],
71+
},
4572
});
4673
});
4774
}

src/components/post-tags.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
4+
import InlineList from './inline-list';
5+
6+
const PostTagsStyle = styled(InlineList)`
7+
margin-left: 0;
8+
padding: 0;
9+
10+
&:before {
11+
content: 'Tags:';
12+
font-weight: bold;
13+
font-size: 0.9em;
14+
margin-right: 0em;
15+
}
16+
17+
li {
18+
font-size: 0.9em;
19+
margin-right: 0;
20+
}
21+
22+
li:before {
23+
content: '#';
24+
}
25+
26+
li:after {
27+
content: ', ';
28+
}
29+
30+
li:last-child:after {
31+
content: '';
32+
}
33+
`;
34+
35+
function Tag({ children }) {
36+
return <>{children}</>;
37+
}
38+
39+
export default function PostTags({ tags }) {
40+
return (
41+
<PostTagsStyle>
42+
{tags.map((tag, i) => (
43+
<li key={i}>
44+
<a href={`/blog/tags/${tag}`}>{tag}</a>
45+
</li>
46+
))}
47+
</PostTagsStyle>
48+
);
49+
}
50+
51+
PostTags.Tag = Tag;

src/components/tags.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ function Tag({ children }) {
4141
export default function Tags({ children }) {
4242
return (
4343
<TagsStyle>
44-
{children.map((child, i) => (
44+
{children?.map((child, i) => (
4545
<li key={i}>{child}</li>
4646
))}
4747
</TagsStyle>

src/pages/blog.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Link from 'gatsby-link';
44
import Layout from '../layouts';
55
import { Helmet } from 'react-helmet';
66
import moment from 'moment';
7+
import PostTags from '../components/post-tags';
78

89
export default function Index({ data }) {
910
const { siteMetadata } = data.site;
@@ -26,14 +27,19 @@ export default function Index({ data }) {
2627
);
2728

2829
return (
29-
<div key={post.id}>
30+
<div key={post.id} style={{ marginBottom: '2em' }}>
3031
<h2>
3132
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link>
3233
</h2>
34+
3335
<p>
3436
<span style={{ fontStyle: 'italic' }}>{formattedDate}</span>{' '}
3537
&mdash; {post.excerpt}
3638
</p>
39+
40+
{post.frontmatter.tags && (
41+
<PostTags tags={post.frontmatter.tags} />
42+
)}
3743
</div>
3844
);
3945
})}
@@ -51,6 +57,7 @@ export const pageQuery = graphql`
5157
title
5258
date
5359
path
60+
tags
5461
}
5562
}
5663
}

src/pages/blog/bash-script-before-reading-code.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
path: /blog/bash-script-before-reading-code
33
title: 'The Bash Script I Use Before Reading Any Code'
44
date: 2026-06-19
5+
tags: ['git', 'tools', 'bash']
56
---
67

78
I saw this awesome blog post [The Git Commands I Run Before Reading Any Code](https://piechowski.io/post/git-commands-before-reading-code/) detailing some git commands to help you navigate a new code base. Well, I decided to make a wrapper bash script around those commands.

src/pages/blog/modern-page-objects.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
path: /blog/modern-page-objects
33
title: Modern Page Objects
44
date: 2018-03-23
5+
tags: ['browser-automation', 'page-objects']
56
---
67

78
The page object model is a pattern for abstracting defining and interacting with components in a GUI.

src/pages/blog/testing-cloudwatch-alarms.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
path: /blog/testing-cloudwatch-alarms
33
title: Testing Cloudwatch Alarms
44
date: 2018-07-09
5+
tags: ['testing', 'aws']
56
---
67

78
## The Problem

src/pages/blog/unit-testing-required.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
path: /blog/unit-testing-required
33
title: Unit Testing Required
44
date: 2018-01-26
5+
tags: ['testing', 'unit-tests']
56
---
67

78
Its been my experience that most companies do not practice unit testing at all. Sometimes its an Agile environment where devs are pushing to increase velocity so they skip writing unit tests offloading the responsibility to functional testing via Selenium. Other times devs just refuse to write any tests because management doesn't make them. The result is the same however. Poorly written code, increasingly complex testing scenarios and difficult deployments. These are all red flags that you aren't testing the write things in the right ways.

src/templates/post.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { graphql } from 'gatsby';
33
import { Helmet } from 'react-helmet';
44
import Layout from '../layouts';
55
import moment from 'moment';
6+
import PostTags from '../components/post-tags';
67

78
export default function Template({ data }) {
89
const { siteMetadata } = data.site;
910
const { frontmatter, html, excerpt } = data.markdownRemark;
10-
const { title, date } = frontmatter;
11+
const { title, date, tags } = frontmatter;
1112

1213
const formattedDate = moment(date).format('MMMM Do YYYY');
1314

@@ -28,7 +29,9 @@ export default function Template({ data }) {
2829

2930
<div className="blog-post-container">
3031
<div className="blog-post">
31-
<h1>{title}</h1>
32+
<h1 style={{ marginBottom: tags ? 0 : '0.5em' }}>{title}</h1>
33+
34+
{tags && <PostTags tags={tags} />}
3235

3336
<p
3437
style={{
@@ -63,6 +66,7 @@ export const pageQuery = graphql`
6366
frontmatter {
6467
date
6568
title
69+
tags
6670
}
6771
}
6872
site {

src/templates/tag.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react';
2+
import { Link, graphql } from 'gatsby';
3+
import Helmet from 'react-helmet';
4+
import moment from 'moment';
5+
import Layout from '../layouts';
6+
7+
export default function TagTemplate({ pageContext, data }) {
8+
const { tag, posts } = pageContext;
9+
const { siteMetadata } = data.site;
10+
11+
// We already have the paths, but we also want title+date for display.
12+
// Pull the needed fields for *only* the posts that belong to this tag.
13+
const postData = data.allMarkdownRemark.edges.map(e => e.node);
14+
15+
return (
16+
<Layout>
17+
<Helmet>
18+
<title>{`Tag: ${tag} - ${siteMetadata.title}`}</title>
19+
</Helmet>
20+
21+
<h1>Posts tagged “{tag}</h1>
22+
23+
<ul style={{ listStyle: 'none', padding: 0 }}>
24+
{postData.map(({ frontmatter, excerpt, fields, html }) => (
25+
<li key={frontmatter.path} style={{ marginBottom: '2rem' }}>
26+
<h2>
27+
<Link to={frontmatter.path}>{frontmatter.title}</Link>
28+
</h2>
29+
<p style={{ fontStyle: 'italic' }}>
30+
{moment(frontmatter.date).format('MMMM Do YYYY')}
31+
</p>
32+
<p>{excerpt}</p>
33+
</li>
34+
))}
35+
</ul>
36+
</Layout>
37+
);
38+
}
39+
40+
// Query only the posts whose path is in the context‐provided array
41+
export const pageQuery = graphql`
42+
query($posts: [String!]!) {
43+
site {
44+
siteMetadata {
45+
title
46+
}
47+
}
48+
allMarkdownRemark(filter: { frontmatter: { path: { in: $posts } } }) {
49+
edges {
50+
node {
51+
frontmatter {
52+
title
53+
path
54+
date
55+
}
56+
excerpt(pruneLength: 140)
57+
}
58+
}
59+
}
60+
}
61+
`;

0 commit comments

Comments
 (0)