A massive library of books with stairs all over. There are nice sitting areas to read on.

Content

This one didn't go as planned! Jump to the end Decisions, Decisions to see the sordid details!

This implementation happened in Gatsby 2 days. At the moment, the site is still in Gatsby 2.

Also, I need a plugin so I can link to headings in a document which I have not installed as of yet! If you want to go to Decisions, Decisions, it's down there a bit...you'll find it!

Content Configuration

  • Add folders for content/blog, content/project, and content/site
  • Add filesystem configuration in gatsby-config.js for these new areas
  • Install and configure Mdx support for Gatsby
  • Install and configure gatsby-remark-images as well. Some dependencies were already in there.
  • Brought in a synthesizer image as a placeholder if there is no specified cover image. Probably should have a default for music, coding, blog post, site post!

Mdx Support for Gatsby

1npm install gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react mdx-utils
2# probably with filesystem at the same time
3npm install gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react gatsby-source-filesystem
gatsby-config.js
1module.exports = {
2 plugins: [
3 {
4 resolve: `gatsby-plugin-mdx`,
5 options: {
6 extensions: [`.mdx`, `.md`],
7 },
8 },
9 {
10 resolve: `gatsby-source-filesystem`,
11 options: {
12 path: `${__dirname}/posts`,
13 name: `posts`,
14 },
15 },
16 ],
17}

Steal From a Tutorial

Stealing from Skillthrive's Gatsby JS: Build a Blog tutorial

Create Pages

Add just enough code to gather slugs and ids to create pages.

gatsby-node.js
1exports.createPages = async ({ graphql, actions }) => {
2 const { data } = await graphql(`
3 query {
4 allMdx(sort: { fields: frontmatter___date, order: DESC }) {
5 edges {
6 node {
7 frontmatter {
8 slug
9 }
10 id
11 }
12 }
13 }
14 }
15 `)
16
17 // Create single blog posts
18 data.allMdx.edges.forEach((edge) => {
19 const slug = edge.node.frontmatter.slug
20 const id = edge.node.id
21 actions.createPage({
22 path: slug,
23 component: require.resolve('./src/templates/blog-post.js'),
24 context: { id },
25 })
26 })
27}

Blog Post Template

Create a template for all posts (like singlePost.js)

blog-post.js
1import * as React from 'react'
2import { graphql } from 'gatsby'
3import { MDXRenderer } from 'gatsby-plugin-mdx'
4import SEO from '../components/seo'
5import { Layout, CoverImage } from '../components'
6
7const BlogPost = ({ data }) => {
8 const coverImage = data.mdx.frontmatter.coverImage.childImageSharp.fluid
9
10 return (
11 <Layout>
12 <SEO title={data.mdx.frontmatter.title} description={data.mdx.frontmatter.excerpt} />
13 <CoverImage fluid={coverImage} />
14 <h1>{data.mdx.frontmatter.title}</h1>
15 <MDXRenderer>{data.mdx.body}</MDXRenderer>
16 </Layout>
17 )
18}
19
20export default BlogPost
21
22export const blogQuery = graphql`
23 query SinglePostQuery($id: String!) {
24 mdx(id: { eq: $id }) {
25 body
26 frontmatter {
27 date(formatString: "MM/DD/YYYY")
28 excerpt
29 slug
30 title
31 tags
32 coverImage {
33 publicURL
34 childImageSharp {
35 fluid {
36 ...GatsbyImageSharpFluid
37 }
38 }
39 }
40 }
41 timeToRead
42 }
43 }
44`

Create CoverImage Component

Using a bit of Tailwind but not that much styling yet (like FeatureImage.js)

CoverImage.js
1import * as React from 'react'
2import Img from 'gatsby-image'
3import { useStaticQuery, graphql } from 'gatsby'
4
5export const CoverImage = ({ fluid }) => {
6 const data = useStaticQuery(graphql`
7 query {
8 imageSharp(fluid: { originalName: { eq: "steve-harvey-xWiXi6wRLGo-unsplash.jpg" } }) {
9 fluid {
10 ...GatsbyImageSharpFluid
11 }
12 }
13 }
14 `)
15
16 return (
17 <div className='overflow-hidden relative'>
18 <Img className='absolute top-0 left-0 w-full h-full' fluid={fluid ? fluid : data.imageSharp.fluid} />
19 </div>
20 )
21}

Create Fake Content

  • Added a couple of fakes to blog
  • Added a very sparse fake to site and project, with no images!

Retrospective

⬜ Links in Mdx are not styled with ExternalLink component. It would be nice not to have to embed that as React component and have it automatically translate the link to it. Clicking on them now does not open a new tab either.
✅ If there is no coverImage, you're hosed!
⬜ Code tests look poor - as expected
⬜ Table test looks poor - as expected
⬜ Post is not styled that great - Heading 1 and 2 are valid, all other headings are the same.
⬜ Horizontal rules do not exist
⬜ Quotes aren't happening
⬜ Ordered lists have no numbers
✅ filenames are off the root instead of blog/first-fake, it is /first-fake. You can enter that in the slug but it would be better not to do that!

  • in gatsby-node.js there is code that takes the slug and creates the path for the page
  • I've got example code on creating a new Node Field that could be used (collection is a good name!)
Attributions

Photo by Niklas Ohlrogge (@ohlrogge) on Unsplash

Decisions, Decisions

Optional reading

Yes, I realize the whole site and in fact Internet is optional reading, but this really is! These are just some of the initial thoughts I went through when trying to figure out how to configure the site for content.

Back to the top!

Here's all the thought process that went into starting this one!

Folder Structure

After studying numerous Gatsby Blog starters (3 or 4) I've decided on the following.

content folder will live at the root with src and not in src.

  • content/blog - All the blog posts
  • content/project - All the music and coding projects
  • content/site - All the site design history

What about src/images? It appears that is where site-wide images will go. Other images will go into the appropriate folder of the article.

As for the content folders, I think I will go with a Year folder and then have a folder inside for each post in the format (adjusted a bit): MM-DD - Blog Post Title. First might be in content/site/2020/12-19 - It lives

Gatsby-source-filesystem Config

With all that in mind, should there be one file system for content or should there be three—one for each type: blog, project, site.

Looks like I should have three, one for each, according to a Stack Overflow answer. If each content type could have different frontmatter structures, this would be smart. What follow are a couple of code examples.

  • You can create a file type based on the source name in gatsby-node
gatsby-node.js (excerpt)
1exports.onCreateNode = ({ node, actions, getNode }) => {
2 const { createNodeField } = actions
3 if (node.internal.type === `MarkdownRemark` || node.internal.type === `Mdx`) {
4 createNodeField({
5 name: `collection`,
6 node,
7 // This doesn't work (without getNode being passed - I forgot!
8 value: getNode(node.parent).sourceInstanceName
9 });
10 })
11};
  • Then you can filter the graphql query for collection of contents based on type
1query postsOnly {
2 allMdx(filter: { collection: { eq: "posts" } }) {
3 edges {
4 node {
5 id
6 collection
7 }
8 frontmatter {
9 title
10 }
11 }
12 }
13}

Even Better

sourceInstanceName already exists, but you cannot filter on this with allMdx (allFile, yes). But, since I got collection working above, now I have duplicate paths to the same data.

  • Add to graphql query
1query {
2 allMdx(sort: { fields: frontmatter___date, order: DESC }) {
3 edges {
4 node {
5 id
6 frontmatter {
7 slug
8 }
9 parent {
10 ... on File {
11 sourceInstanceName
12 }
13 }
14 }
15 }
16 }
17}
  • Generate the path with it
gatsby-node.js (excerpt)
1// Create single blog posts
2data.allMdx.edges.forEach((edge) => {
3 const slug = edge.node.frontmatter.slug
4 const id = edge.node.id
5 const collection = edge.node.parent.sourceInstanceName
6
7 actions.createPage({
8 path: `${collection}/${slug}`,
9 component: require.resolve('./src/templates/blog-post.js'),
10 context: { id },
11 })
12})

Final Answer

Going with onCreateNode to add a collection field. It works for creating the path and it works for filtering later.

  • Final GraphQL query
1query {
2 allMdx(sort: { fields: frontmatter___date, order: DESC }) {
3 edges {
4 node {
5 id
6 frontmatter {
7 slug
8 }
9 fields {
10 collection
11 }
12 }
13 }
14 }
15}

Displaying Content

On Home Page

Home page should display feature content for blog and project. These will be sorted by featured field in frontmatter. If there is no featured field, the post/project will not appear.

Featured Blog Posts

  • blah (1)
  • blah (2)
  • blah (3)

Featured Projects

  • blah (1)
  • blah (2)
  • blah (3)

On the Main Page for Content Type

For blog, project, and site main page, a reverse chronological list of posts.

On a Tag Page

For each tag, a list of matching files of all content type with something that indicates the type. Perhaps a filter to include all or just a subset? These should be reverse chronological too.

i.e. gatsby tag

  • Site: {Name of post}
  • Blog: {Name of post}
  • Project: {Name of project}
Built with Gatsby from 2020 thru 2024 (and beyond?!)