惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
P
Proofpoint News Feed
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
News | PayPal Newsroom
Application and Cybersecurity Blog
Application and Cybersecurity Blog
O
OpenAI News
S
Security @ Cisco Blogs
宝玉的分享
宝玉的分享
Hacker News: Ask HN
Hacker News: Ask HN
T
Troy Hunt's Blog
Google Online Security Blog
Google Online Security Blog
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
TaoSecurity Blog
TaoSecurity Blog
Help Net Security
Help Net Security
Latest news
Latest news
NISL@THU
NISL@THU
S
Security Affairs
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 聂微东
AI
AI
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Recent Announcements
Recent Announcements
P
Privacy & Cybersecurity Law Blog
小众软件
小众软件
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
博客园 - 司徒正美
AWS News Blog
AWS News Blog
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
I
InfoQ
Schneier on Security
Schneier on Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Exploit Database - CXSecurity.com
IT之家
IT之家
T
Threatpost
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
News and Events Feed by Topic
L
LINUX DO - 最新话题
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
H
Heimdal Security Blog
S
SegmentFault 最新的问题

Chip Cullen

The need for importance, and AI: Chip Cullen An updated Colorosetta: Chip Cullen The Return of the Font Combinator!: Chip Cullen Changing the number of an item in an ordered list: Chip Cullen My pizza dough recipe as of May 2025: Chip Cullen Gonna try to be a bit more personal: Chip Cullen How I built dynamic social media images in Eleventy using Cloudinary: Chip Cullen My current approach to AI : Chip Cullen Lessons Learned Surviving a Major Product Launch: Chip Cullen How to Build a Drop Down Menu with Modern CSS: Chip Cullen How to stop page scrolling when you have an open dialog element: Chip Cullen Distraction Driven Development: Chip Cullen How I learned to code: the art of letting go: Chip Cullen In praise of the switch statement: Chip Cullen Project stuck? Think about how you’re breaking it down & question everything: Chip Cullen So how did the onboarding experiment go?: Chip Cullen Ideas for an Onboarding Checklist: Chip Cullen I really like Post Mortems: Chip Cullen Raise Red Flags Early: Chip Cullen How to mock fetch requests in React Testing Librarty tests: Chip Cullen Running a Structured Meeting: Chip Cullen Adding the View Transitions API to my personal site: Chip Cullen A Lightweight Way to Read GraphQL Data: Chip Cullen How to make a color changing favicon: Chip Cullen Using a Pros/Cons list to help navigate technical discussions: Chip Cullen How to use variable fonts from Google Fonts: Chip Cullen A new website: now on Eleventy!: Chip Cullen How to Truncate Type at More Than One Line with Just CSS: Chip Cullen Colorosetta: the VS Code Extension!: Chip Cullen Using CSS Custom Properties and Logical Properties Together: Chip Cullen Browser Dev Tools: Element Inspector Popover: Chip Cullen The Link with rel=preload is a Seperate Thing: Chip Cullen How to have Dark & Light Mode Images that also works with User Choice: Chip Cullen Don’t use Viewport Units for Font Size on their own: Chip Cullen A little known Media Query: Aspect Ratio: Chip Cullen Meta thinking: Managing Decisions: Chip Cullen Give Your To-Do's Context: Chip Cullen Say What the Impact is when Reporting Issues: Chip Cullen Firefighting 101: How to Manage Breakages: Chip Cullen How to Deal With Large Pieces of Technical Debt: Chip Cullen Make Your Request Clear: Chip Cullen Analytics events, HTML classes, and protecting against refactoring: Chip Cullen How We Removed jQuery from a large app: Chip Cullen New tool: ColoRosetta: Chip Cullen What width and height attributes should you use with responsive images?: Chip Cullen Django 3.1 gotcha: Referrer Policy has a new default, and it might break iframes and links: Chip Cullen A Javascript Component Pattern: Chip Cullen CSS min(), max() and clamp() Functions: Chip Cullen Pointer Events and Inline Elements in Chrome: Chip Cullen Resolving a github repo and a new Create React App: Chip Cullen How to POST *Data* with the Fetch API: Chip Cullen The Contrast Triangle: Chip Cullen Advice on interviewing for Junior Developers: Chip Cullen Life Lessons Learned From Running a Marathon: How to do something really hard: Chip Cullen A (Brief) intro to Search Engine Structured Data: Chip Cullen Javascript Fallback Values on Variables and Booleans - a hard lesson: Chip Cullen Alfred Tip: Quickly Access Common URLs: Chip Cullen Responsive Images in Hugo - by Laura Kalbag: Chip Cullen How to Create and Use Fixtures in Cypress Tests: Chip Cullen Fixing the 'Bad Interpreter' Error from AWS and Python 3.7: Chip Cullen Creating a Canonical Tag in a Django Template: Chip Cullen Responsive spacing with viewport and ch units: Chip Cullen Welcome to my New Design - 2019: Chip Cullen Django Templates: Block and If statements don’t work like you might expect: Chip Cullen Books I Read in 2018: Chip Cullen Lifehack: 4 ways to help tame common email noise: Chip Cullen How to make better Pull Requests: Adding Steps to Test: Chip Cullen The unsung develpment tool: Spreadsheets: Chip Cullen Troubleshooting Adding and Removing EventListeners: with Arguments, Debounced, and in a React Class: Chip Cullen How to Fake the Window Object in Jest and Enzyme: Chip Cullen Migrating From Wordpress to Hugo: Chip Cullen Background Repeat and its Possibilities: Chip Cullen Getting Started With Front End Tests: a Mindset: Chip Cullen Migrating a Blog - An Opportunity for a Content Inventory: Chip Cullen Moving to Hugo: Chip Cullen JavaScript events: .target vs .currentTarget: Chip Cullen Things I wish I knew when starting with Python: Chip Cullen Leading Ampersands for modifiers in Sass: An anti-pattern: Chip Cullen How to get rid of the "You have mail" message in your terminal: Chip Cullen Why three typefaces rule the web, and what you can do about it: Chip Cullen You shouldn't worry about Section 508 - it's Section 504: Chip Cullen Looping Video Backgrounds: pointers and pitfalls: Chip Cullen How to “preview” a click event tag in the Google Tag Manager console: Chip Cullen Moving on from a technology, or: life after Drupal: Chip Cullen Don’t be a dumb developer: Chip Cullen Two level breadcrumbs with CSS :only-child: Chip Cullen Simplicity comes with experience: Chip Cullen Do the least amount possible: Chip Cullen SVGs vs. Icon Fonts: Two points in favor of Icon Fonts: Chip Cullen Accessible links without underlines: Chip Cullen The Strategic Job Hunt: Chip Cullen Surviving Getting Laid Off: Chip Cullen How to structure your typography in Sass: Chip Cullen Layer Cake: A Responsive Design Layout Pattern: Chip Cullen Creativity is yet to come in Web Design: Chip Cullen Front End Testing with Wraith: A Step by Step Recipe: Chip Cullen Where to begin? How I start a visual design for the web: Chip Cullen If you could only have five Google Fonts: Chip Cullen Why SVG is so cool (or: what happens when you're late to the party on something): Chip Cullen How to apply classes to elements with CKEditor 4, in Drupal 7: Chip Cullen
Making a Gatsby Site with Multiple Content Types: Chip Cullen
2019-04-29 · via Chip Cullen

I was building a Gatsby site at work recently, and had a really hard time doing something which is usually pretty easy with static site generators. That was to define multiple content types, with Markdown files as sources, like this:

/src/
    /posts/
        post1.md
        post2.md
    /pages/
        about.md

Now, this will work:

/src/
    /posts/
        post1.js
        post2.js
    /pages/
        about.js

But writing blog posts in JavaScript is no way to live your life.

Assumptions

  • You have a local Gatsby site up and running
  • You are at least somewhat aware of GraphQL, or at least how to use it in Gatsby. If you're not, I suggest the Level Up Tutorials course on Gatsby.

At the time of this writing, Gatsby 2 was the latest version.

Updating your Gatsby Config file

The first thing that you will need to do is to update gatsby-config.js (found at the root level of your project) so that Gatsby knows to look in both directories.

module.exports = {
  siteMetadata: {
    ...
  },
  pathPrefix: `...`,
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/pages`,
        name: `pages`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/posts`,
        name: `posts`,
      },
    },
    ...
    // other plugin configs go here
  ],
};

What you're doing is telling Gatsby's file system plugin about both content directories. And for each, you are specifying the path to the files, as well as a name that will be important in a moment.

The name can be whatever you want, but I'm guessing that you'll want it to agree with whatever the directory is called.

Updating the Gatsby Node file

You will now need to update your gatsby-node.js file, also found in your project root.

Here is what the whole thing will look like (I will explain more after):

const _ = require("lodash");
const Promise = require("bluebird");
const path = require("path");

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions;

  if (_.get(node, "internal.type") === `MarkdownRemark`) {
    // Get the parent node
    const parent = getNode(_.get(node, "parent"));

    createNodeField({
      node,
      name: "collection",
      value: _.get(parent, "sourceInstanceName")
    });
  }
};

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions;

  return new Promise((resolve, reject) => {
    graphql(`
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 1000
        ) {
          edges {
            node {
              fields {
                collection
              }
              frontmatter {
                slug
                title
              }
            }
          }
        }
      }
    `).then(results => {
      const allEdges = results.data.allMarkdownRemark.edges;

      const blogEdges = allEdges.filter(
        edge => edge.node.fields.collection === `posts`
      );
      const pageEdges = allEdges.filter(
        edge => edge.node.fields.collection === `pages`
      );

      _.each(blogEdges, (edge, index) => {
        const previous =
          index === blogEdges.length - 1 ? null : blogEdges[index + 1].node;
        const next = index === 0 ? null : blogEdges[index - 1].node;

        createPage({
          path: `/posts/${edge.node.frontmatter.slug}`,
          component: path.resolve("./src/layouts/Post.js"),
          context: {
            permalink: edge.node.frontmatter.slug,
            previous,
            next
          }
        });
      });

      _.each(pageEdges, (edge, index) => {
        createPage({
          path: `/${edge.node.frontmatter.slug}`,
          component: path.resolve("./src/layouts/Page.js"),
          context: {
            permalink: edge.node.frontmatter.slug
          }
        });
      });

      resolve();
    });
  });
};

Dependencies

The first thing you will notice is the dependencies at the top of the file. If you don't already have lodash and bluebird, you will have to add them via npm install:

npm install -s lodash bluebird

However, path comes from Node directly, so you don't need to add that. One thing to keep in mind is that this file is a set of instructions for Gatsby's node environment.

onCreateNode

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions;

  if (_.get(node, "internal.type") === `MarkdownRemark`) {
    // Get the parent node
    const parent = getNode(_.get(node, "parent"));

    createNodeField({
      node,
      name: "collection",
      value: _.get(parent, "sourceInstanceName")
    });
  }
};

What this does is go through our files, and if there was a name specified in gatsby-config, we add that as a field to our content. So, now when we specified name: 'pages', that will be available to us on each of our nodes.

I'm not sure why Gatsby makes you bend over backwards to add this (that's what sourceInstanceName is, so it's already known), but for now we need to manually add this metadata to each file.

createPages

Finally, we will create the pages. Basically, what we will do is:

  • Go through all the files that we know about
  • Split them up by their name
  • Associate each content type with an appropriate template, and supply context information when necessary

This also takes place in the gatsby-node.js file, so it is right under the onCreateNode function that we already have.

Going through all the Markdown files

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions;

  return new Promise((resolve, reject) => {
    graphql(`
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 1000
        ) {
          edges {
            node {
              fields {
                collection
              }
              frontmatter {
                slug
                title
              }
            }
          }
        }
      }
    `).then(results => {

So what we're doing here is using GraphQL, we're going through all of our Markdown files and gathering information about each file.

Because of what we did in onCreateNode our markdown files now have a field called collection (which corresponds to the name in our config).

node {
  fields {
    collection
  }

Now we can use it!

Breaking up the content types

This is the part that deviates from the "I'm only writing a blog" scenario that you typically find in Gatsby tutorials. We have a bunch of markdown files, but we want to do different things with them.

const allEdges = results.data.allMarkdownRemark.edges;

const blogEdges = allEdges.filter(
  edge => edge.node.fields.collection === `posts`
);
const pageEdges = allEdges.filter(
  edge => edge.node.fields.collection === `pages`
);

What I'm doing here is grabbing all of the results from our GraphQL query as allEdges, then filtering that into two new arrays of edges — blogEdges and pageEdges. We filter those by the collection field. This will allow us to treat both sets of content in different ways.

If you have other content types that you want to make (e.g., recipes, bios, etc), this is how you handle it. You just make sure that the collection agrees with the name from your config file.

Handling Blog Posts

_.each(blogEdges, (edge, index) => {
  const previous =
    index === blogEdges.length - 1 ? null : blogEdges[index + 1].node;
  const next = index === 0 ? null : blogEdges[index - 1].node;

  createPage({
    path: `/posts/${edge.node.frontmatter.slug}`,
    component: path.resolve("./src/layouts/Post.js"),
    context: {
      permalink: edge.node.frontmatter.slug,
      previous,
      next
    }
  });
});

Now that we have just our blog posts (blogEdges), we go through each of them (using lodash's .each method). We determine the previous and next edge so that we can pass that information along as part of the context.

In createPage (which is a standard Gatsby function) we point to the path that we want each post to have, the component which is the template for the post, and create context.

We pass the previous and next edges in context so that when we build our page, we can also construct pagination that lets the user go forward / backward through the blog posts.

So, in our Post template, we can have something along the lines of this:

<Link to={`/posts/${next.frontmatter.slug}`} rel="next">
  {next.frontmatter.title}</Link>

This is a big reason why we're breaking up blog posts and pages into blogEdges and pageEdges — we can determine the previous and next just for blog posts. If we didn't, pages that we created would get mixed into the pagination of our blog posts, which ... ain't right.

Handling Pages

      _.each(pageEdges, (edge, index) => {
        createPage({
          path: `/${edge.node.frontmatter.slug}`,
          component: path.resolve("./src/layouts/Page.js"),
          context: {
            permalink: edge.node.frontmatter.slug
          }
        });
      });

      resolve();
    });
  });
};

Pages, though, are totally different. We're basically doing the same thing, but without the previous and next business, because our pages are not being presented with pagination.

Conclusion

I hope this walk through helps you, if you're looking to have multiple content types in a Gatsby Site. I've found that this solution works really well. You can even still create some-page.js type files in your pages directory and those will still build pages - which is great if you really need something bespoke.