Graph QL
February 16, 2018
So the tutorials I used to teach myself a bit about Gatsby have assumed a single source for your blog posts and a structure that has your homepage serve as the "index" in RESTful terms, listing links to all of your blog posts. unfortunately, I want my site to have both portfolio and blog post links to start and I think the separation is important.
So it looks like I have to bump up my GraphQL education on the timetable.
The solution is to add in a filter to your GraphQL query and make sure that your separate template files have different names and filters. This is the solution on the GraphQL side I ended up stealing simply using the folder holding the file and some clever regex to parse what I needed https://github.com/gatsbyjs/gatsby/issues/1634.
The filter is: filter: {fileAbsolutePath: {regex: "/blog/.*\\.md$/"}}
but be sure to see how the query is structured to understand what is going on. I actually ended up swtiching the filter and sort bits because I had a hunch that the filter ought to go first so we arent sorting the full list and then filtering out items, but I hadn't tested yet. My porfolio is so outnumbered by blog posts, and the absolute number of both so small, that I don't know that it matters anyway.
My project thus is organized in part as:
..
/src
/layouts
/pages
/blog
/YYYY-MM-DD-blog-title
index.md
/portfolio
/YYYY-MM-DD-project-title
index.md
/templates
/utils
My blog.js
that lives in the \pages
root looks like this
src/pages/blog.js
import React from "react";
import Link from "gatsby-link";
import Helmet from "react-helmet";
export default function Index({ data }) {
const { edges: posts } = data.allMarkdownRemark;
return (
<div className="blog-posts">
<Helmet title={`Blog posts: rusl.io & Russell Schmidt`} />
{posts
.filter(post => post.node.frontmatter.title.length > 0)
.map(({ node: post }) => {
return (
<div className="blog-post-preview" key={post.id}>
<h1>
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link>
</h1>
<h2>{post.frontmatter.date}</h2>
<p>{post.excerpt}</p>
</div>
);
})}
</div>
);
}
export const pageQuery = graphql`
query BlogQuery {
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
filter: {fileAbsolutePath: {regex: "/blog/.*\\.md$/"}}
) {
edges {
node {
excerpt(pruneLength: 250)
id
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
path
}
}
}
}
}
`;
And my portfolio.js
that also lives in the \pages
root looks like this:
src/pages/portfolio.js
import React from "react";
import Link from "gatsby-link";
import Helmet from "react-helmet";
export default function Index({ data }) {
const { edges: posts } = data.allMarkdownRemark;
return (
<div className="blog-posts">
<Helmet title={`Portfolio Projects: rusl.io & Russell Schmidt`} />
{posts
.filter(post => post.node.frontmatter.title.length > 0)
.map(({ node: post }) => {
return (
<div className="blog-post-preview" key={post.id}>
<h1>
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link>
</h1>
<h2>{post.frontmatter.date}</h2>
<p>{post.excerpt}</p>
</div>
);
})}
</div>
);
}
export const pageQuery = graphql`
query PortfolioQuery {
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
filter: {fileAbsolutePath: {regex: "/portfolio/.*\\.md$/"}}
) {
edges {
node {
excerpt(pruneLength: 250)
id
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
path
}
}
}
}
}
`;
Then there are the two template files in ../src/templates
, blog-post.js
and portfolio-post.js
.
src/templates/blog-post.js
import React from "react";
import Helmet from "react-helmet";
export default function Template({
data
}) {
const post = data.markdownRemark;
return (
<div className="blog-post-container">
<Helmet title={`rusl.io - ${post.frontmatter.title}`} />
<div className="blog-post">
<h1>{post.frontmatter.title}</h1>
<div
className="blog-post-content"
dangerouslySetInnerHTML={{ __html: post.html }}
/>
</div>
</div>
);
}
export const pageQuery = graphql`
query BlogPostByPath($path: String!) {
markdownRemark(frontmatter: { path: { eq: $path } }) {
html
frontmatter {
date(formatString: "MMMM DD, YYYY")
path
title
}
}
}
`
;
src/templates/portfolio.js
import React from "react";
import Helmet from "react-helmet";
export default function Template({
data
}) {
const post = data.markdownRemark;
return (
<div className="blog-post-container">
<Helmet title={`rusl.io - ${post.frontmatter.title}`} />
<div className="blog-post">
<h1>{post.frontmatter.title}</h1>
<div
className="blog-post-content"
dangerouslySetInnerHTML={{ __html: post.html }}
/>
</div>
</div>
);
}
export const pageQuery = graphql`
query PortfolioPostByPath($path: String!) {
markdownRemark(frontmatter: { path: { eq: $path } }) {
html
frontmatter {
date(formatString: "MMMM DD, YYYY")
path
title
}
}
}
`
;
Lastly, you will want to add some logic to your gatsby-node.js
file.
const path = require('path');
exports.createPages = ({ boundActionCreators, graphql }) => {
const { createPage } = boundActionCreators;
const blogPostTemplate = path.resolve(`src/templates/blog-post.js`);
const portfolioPostTemplate = path.resolve(`src/templates/portfolio-post.js`);
return graphql(`{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 1000
) {
edges {
node {
excerpt(pruneLength: 250)
html
id
frontmatter {
date
path
title
type
}
}
}
}
}`).then(result => {
if (result.errors) {
return Promise.reject(result.errors);
} result.data.allMarkdownRemark.edges
.forEach(({ node }) => {
if (node.frontmatter.type === "blog") {
createPage({
path: node.frontmatter.path,
component: blogPostTemplate,
context: {} // additional data can be passed via context
});
} else if (node.frontmatter.type === "portfolio") {
createPage({
path: node.frontmatter.path,
component: portfolioPostTemplate,
context: {} // additional data can be passed via context
});
}
});
});
}