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 ingatsby-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-utils2# probably with filesystem at the same time3npm install gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react gatsby-source-filesystem
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.
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 slug9 }10 id11 }12 }13 }14 }15 `)16
17 // Create single blog posts18 data.allMdx.edges.forEach((edge) => {19 const slug = edge.node.frontmatter.slug20 const id = edge.node.id21 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
)
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.fluid9
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 BlogPost21
22export const blogQuery = graphql`23 query SinglePostQuery($id: String!) {24 mdx(id: { eq: $id }) {25 body26 frontmatter {27 date(formatString: "MM/DD/YYYY")28 excerpt29 slug30 title31 tags32 coverImage {33 publicURL34 childImageSharp {35 fluid {36 ...GatsbyImageSharpFluid37 }38 }39 }40 }41 timeToRead42 }43 }44`
Create CoverImage Component
Using a bit of Tailwind but not that much styling yet (like FeatureImage.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 ...GatsbyImageSharpFluid11 }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.
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
1exports.onCreateNode = ({ node, actions, getNode }) => {2 const { createNodeField } = actions3 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).sourceInstanceName9 });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 id6 collection7 }8 frontmatter {9 title10 }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 id6 frontmatter {7 slug8 }9 parent {10 ... on File {11 sourceInstanceName12 }13 }14 }15 }16 }17}
- Generate the path with it
1// Create single blog posts2data.allMdx.edges.forEach((edge) => {3 const slug = edge.node.frontmatter.slug4 const id = edge.node.id5 const collection = edge.node.parent.sourceInstanceName6
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 id6 frontmatter {7 slug8 }9 fields {10 collection11 }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}