Multiple templates in Gatsby.js - reactjs

I'm pretty new to Gatsby and want to have multiple templates that i can use for my different pages, eg Landingpage, Contact and About cant have the same templates.
I have made a ugly code in gatsby-node.js for now but it only works for two templates, here is my code (btw I'm sourcing data from markdownfiles):
Mardownfile 1:
---
title: 'Index page'
slug: '/'
template: indexTemplate //This is where i declare which template it shall use
content: 'Index content'
---
Mardownfile 2:
---
slug: '/my-first-post'
date: '2019-05-04'
title: 'My first blog post!'
content: 'Content post 1'
template: postTemplate
---
gatsby-node.js:
exports.createPages = async ({ actions, graphql, reporter }) => {
const { createPage } = actions
const result = await graphql(`
query{
allMarkdownRemark {
nodes {
frontmatter {
slug
template
}
}
}
}
`)
const postTemplate = require.resolve(`./src/templates/postTemplate.js`)
const indexTemplate = require.resolve(`./src/templates/indexTemplate.js`)
const aboutTemplate = require.resolve(`./src/templates/aboutTemplate.js`) // I also want a template for my aboutpage
// Handle errors
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
result.data.allMarkdownRemark.nodes.forEach( post => {
createPage({
path: post.frontmatter.slug,
component: post.frontmatter.template === 'indexTemplate' ? indexTemplate : postTemplate, // Here is where the ugly "magic" happens...
context: {
slug: post.frontmatter.slug,
},
})
})
}

The idea of using gatsby-node.js is to create dynamic pages from some data source (CMS, markdown files, JSON files, external APIs, etc) with the createPage API. In other words, it's really useful to create dynamic pages that have an unknown slug or name from a data source. The idea is to query all types of data (posts for instance) and pass via context some unique information (normally the slug or the id) as a filter parameter to perform another query in your template.
In your case, that fits for your blog (markdown file 2), but not for other known pages such as index, about, etc (markdown file 1). They are defined and known pages and should be treated differently, using a page query. They are not templates because are unique pages, you won't have two different homes that can reuse a home template.
To identify different markdown pages, between posts and home page, for example, you can use a key value to filter your queries.
---
title: 'Index page'
slug: '/'
key: 'home'
template: indexTemplate //This is where i declare which component it shall use
content: 'Index content'
---
Content of the index page.
And:
---
slug: '/my-first-post'
date: '2019-05-04'
key: 'post'
title: 'My first blog post!'
content: 'Content post 1'
template: postTemplate
---
When creating a pages dyamically (via gatsby-node.js) the approach should look like:
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode, basePath: `pages` })
createNodeField({
node,
name: `slug`,
value: slug,
})
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
query {
allMarkdownRemark(
filter: { frontmatter: { key: { eq: "article" }}}) {
edges {
node {
fields {
slug
}
}
}
}
}
`)
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: node.fields.slug,
},
})
})
}
Note the filter for the key value.
Then, in your blog-post (template):
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"
export default function BlogPost({ data }) {
const post = data.markdownRemark
return (
<Layout>
<div>
<h1>{post.frontmatter.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</div>
</Layout>
)
}
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
}
}
}
`
For your static pages, just make the query on the page. For your index.js:
import React from 'react'
import { graphql } from 'gatsby'
const HomePage = ({data}) => {
return (
<div>
Your page title is {data.nodes[0].frontmatter.title}
</div>
)
}
export const query = graphql`
query HomePageQuery {
allMarkdownRemark(filter: { frontmatter: { key: { eq: "home" }}}) {
nodes{
frontmatter{
title
}
}
}
}
`
export default HomePage
Feel free to adapt the code to your needs. The important thing is that you should differentiate between what should be used as a template (blog, posts, articles, or other dynamic data) and what should be handled statically (home, about, or other static pages).
Of course, upon the scope of the question, you can have multiple templates, let's say one for posts and the other for reviews (or other dynamic data). You can simply achieve it by saving your query result inside a variable and call twice the createPage API:
const result1 = await graphql(`
query{
allMarkdownRemark {
nodes {
frontmatter {
slug
template
}
}
}
}
`)
const result2 = await graphql(`
query{
allMarkdownRemark {
nodes {
frontmatter {
slug
template
}
}
}
}
`)
const postTemplate = require.resolve(`./src/templates/postTemplate.js`)
const reviewsTemplate = require.resolve(`./src/templates/reviewsTemplate.js`)
// Handle errors
if (result1.errors || result2.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
result1.data.allMarkdownRemark.nodes.forEach( post => {
createPage({
path: post.frontmatter.slug,
component: postTemplate
context: {
slug: post.frontmatter.slug,
},
})
})
result2.data.allMarkdownRemark.nodes.forEach( review => {
createPage({
path: review.frontmatter.slug,
component: reviewsTemplate
context: {
slug: review.frontmatter.slug,
},
})
})

Related

Gatsby giving error on generating pages from markdown files

I am trying to generate pages from two categories of .md files [projects/posts] , I have combined the query related to both folders in a query as below in gatsby-node.js :
exports.createPage = ({ graphql, actions }) => {
const { createPage } = actions
const blogTemplate = path.resolve("./src/templates/blog-details.js")
const projectTemplate = path.resolve("./src/templates/project-details.js")
return graphql(`
query {
projects: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/projects/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
nodes {
frontmatter {
slug
title
}
fileAbsolutePath
}
}
posts: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/posts/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
nodes {
frontmatter {
slug
title
}
fileAbsolutePath
}
}
}
`).then(result => {
if (result.errors) {
Promise.reject(result.errors)
}
console.log(result)
const projects = result.data.projects.nodes
const posts = result.data.posts.nodes
console.log("starting projects page creation")
console.log(projects)
projects.forEach((node, index) => {
createPage({
path: "/projects/" + node.frontmatter.slug,
component: projectTemplate,
context: { slug: node.frontmatter.slug },
})
})
console.log("starting posts page creation")
console.log(posts)
posts.forEach((node, index) => {
const next = index === 0 ? null : posts[index - 1]
const previous = index === nodes.length - 1 ? null : posts[index + 1]
createPage({
path: "/blog/" + node.frontmatter.slug,
component: blogTemplate,
context: {
slug: node.frontmatter.slug,
previous,
next,
},
})
})
})
}
The query is fetching response , have verified from GraphiQL :
But gatsby develop gives error in creating pages:
Exported queries are only executed for Page components. It's possible you're
trying to create pages in your gatsby-node.js and that's failing for some
reason.
Not able to find the exact reason of the error.
Kindly let me know what I am missing here, thanks.
createPages is an asynchronous action. In addition, you have a typo (notice the trailing "s").
In list of gatsby-node.js APIs you have an onCreatePage function or createPages, but not createPage.
Your createPages should look like:
const path = require("path")
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const queryResults = await graphql(`
query AllProducts {
allProducts {
nodes {
id
name
price
description
}
}
}
`)
const productTemplate = path.resolve(`src/templates/product.js`)
queryResults.data.allProducts.nodes.forEach(node => {
createPage({
path: `/products/${node.id}`,
component: productTemplate,
context: {
// This time the entire product is passed down as context
product: node,
},
})
})
}
As I said, notice the async.

Preparing requested page Gatsby

I'm using Gatsby as a front-end, and Strapi as my back-end to create my website. However, when I clicked my blog, it only shows white blank page, and when I checked it on my localhost, it shows these errors. Anyone have ever experienced this error?
Here is my Gatsby-node.js
const path = require("path")
// create pages dynamically
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
{
blogs: allStrapiBlogs {
nodes {
slug
}
}
}
`)
const prodres = await graphql(`
{
products: allStrapiProduct {
nodes {
slug
}
}
}
`)
result.data.blogs.nodes.forEach(blog => {
createPage({
path: `/blogs/${blog.slug}`,
component: path.resolve(`src/templates/blog-template.js`),
context: {
slug: blog.slug,
},
})
})
prodres.data.products.nodes.forEach(product => {
createPage({
path: `/product/${product.slug}`,
component: path.resolve(`src/templates/product-template.js`),
context: {
slug: product.slug,
},
})
})
}

Gatsby and Woocommerce API – Product category fields stopped syncing in GraphQL

I am building a headless E-commerce application with Gatsby js and the gatsby-source-woocommerce plugin. Products are categorised in two main categories (Woman and Man) with a set of subcategories. Product pages and Category pages are dynamically created using Node and GraphQL.
Everything has been working perfectly but now the main category 'Woman' has suddenly stopped appearing in GraphQL which results in the page /woman being empty. The weird part is that all other categories including subcategories of woman are fine. I've added new categories in the woocommerce back-end to find the issue and nothing will sync. Changing other data works, like product name, prices or images. So basically it appears that the GraphQL is not fetching updates, but only for 'allWcProductsCategories'.
I have tried gatsby clear, i have tried reinstalling dependencies and no warnings or errors appear in run develop. And now I'm going insane. Help?
The gatsby-node.js code (which works perfectly)
const path = require('path')
module.exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const singleTemplate = path.resolve('./src/templates/single.js')
const womanTemplate = path.resolve('./src/templates/woman.js')
const manTemplate = path.resolve('./src/templates/man.js')
const res = await graphql(`
query {
allWcProducts {
edges {
node {
slug
}
}
}
allWcProductsCategories {
edges {
node {
slug
}
}
}
}
`)
res.data.allWcProducts.edges.forEach((edge) => {
createPage({
component: singleTemplate,
path: `/${edge.node.slug}`,
context: {
slug: edge.node.slug
}
})
})
res.data.allWcProductsCategories.edges.forEach((edge) => {
var category = `${edge.node.slug}`;
if (category === 'woman' ) {
var slug = `/${edge.node.slug}`;
createPage({
component: womanTemplate,
path: slug,
context: {
slug: edge.node.slug
}
})
} else if (category.startsWith('woman-')) {
var slug = `/woman/${edge.node.slug}`;
slug = slug.replace('woman-', '')
createPage({
component: womanTemplate,
path: slug,
context: {
slug: edge.node.slug
}
})
} else if (category === 'man' ) {
var slug = `/${edge.node.slug}`;
createPage({
component: manTemplate,
path: slug,
context: {
slug: edge.node.slug
}
})
} else if (category.startsWith('man-')) {
var slug = `/man/${edge.node.slug}`;
slug = slug.replace('man-', '')
createPage({
component: manTemplate,
path: slug,
context: {
slug: edge.node.slug
}
})
}
})
if (process.env.NODE_ENV === 'development') {
process.env.GATSBY_WEBPACK_PUBLICPATH = '/'
}
}
try gatsby clean instead of gatsby clear and make sure you are running the latest version of the cli. gatsby clear is not a cli tool. But if it were a caching problem, you would have old data, not no data.
Other than that, did you console.log edge.node.slug (where you set it to var category)? Are there slugs set to 'woman'? Make sure it isn't 'woman/'.

Where does the variable slug comes from in gatsby graphql query?

We create the slug node in gatsby-node.js. Then we create the createPage
const posts = result.data.allMarkdownRemark.edges
posts.forEach(({ node: post }) => {
createPage({
path: `posts${post.fields.slug}`,
component: PostTemplate,
context: {
slug: post.fields.slug,
testingSomething: "this is a test",
},
})
})
In the template we run something like this.
const PostTemplate = ({ data: post }) => {
return ( ...) }
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
...
}
`
export default PostTemplate
How does graphql know that there's a slug? If it did something like this.props.pageContext.slug fine, but what's going on under the hud?
How does the variable $slug get populated?
To add variables to page component queries, pass these in the context object when creating pages.
Query variables
const posts = result.data.allMarkdownRemark.edges
posts.forEach(({ node: post }) => {
createPage({
path: `posts${post.fields.slug}`,
component: PostTemplate,
context: {
slug: post.fields.slug,
testingSomething: "this is a test",
},
})
})

Gatsby to generate pages when a new variable is served from Contentful CMS

So I built a Gatsby blog with products, and it can programmatically generate pages for every product. I want to add to this project so that it also generates pages for product categories. Each category page will display all products in that category.
The content is served from Contentful CMS.
Is this possible? to add a second layout template and create such pages?
exports.createPages = ({ graphql, boundActionCreators }) => {
const { createPage } = boundActionCreators
return new Promise((resolve, reject) => {
const blogPost = path.resolve('./src/templates/blog-post.js')
const categoryPage = path.resolve('./src/templates/category-post.js')
resolve(
graphql(
`
{
allContentfulBlog(limit: 200) {
edges {
node {
id
categories
mainTitle
mainImg {
id
}
mainText {
childMarkdownRemark {
excerpt
}
}
slug
}
}
}
}
`
)
.then(result => {
if (result.errors) {
console.log(result.errors)
reject(result.errors)
}
result.data.allContentfulBlog.edges.forEach(edge => {
createPage({
path: edge.node.slug,
component: blogPost,
context: {
slug: edge.node.slug,
},
})
})
return
})
.then(result => {
result.forEach(category => {
createPage({
path: '/' + edge.node.category,
component: categoryPage,
context: {
slug: edge.node.category,
},
})
})
})
)
})
}
The first .then() method in the promise works just fine, and my idea is to simply add a second .then(), with the categories pages's template.
Terminal shows:
You could insert this after products pages loop but you don't have data for categories. It's possible to collect it from products but it would be quirky workaround.
You need separate query, separate promise and to compose promises (.all()) at top level.

Resources