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

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",
},
})
})

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.

GatsbyJS: createPage skip pages in WordPress reading settings

I am using GatsbyJS & gatsby-source-wordpress on my site. Is it somehow possible to skip both front page and blog page that has been assigned in reading settings on my WordPress site during the creation of pages?
During research I found a WordPress plugin wp-graphql-homepage by Ash Hitchcock which returns a Page type of the homepage and page for post page set under the reading settings. However, still not quite sure if that should be used to accomplish what I am trying to?
I also found I can query for isPostsPage and isFrontPage. Maybe once I have those, I can chain filter onto allWpPage.nodes when about to run createPages.
e.g., allWpPage.nodes.filter(node => !node.isFrontPage || !node.isPostsPage).map(... ? Just not quite sure how?
The reason why I want to skip front page and post page is because I do have a index.js and blog.js placed in src/pages. Because of this, I do get a error during page creation when running gatsby develop:
{ allWpPage: { nodes: [ [Object], [Object], [Object], [Object] ] } }
unexpected error while SSRing the path: /
TypeError: Cannot read property 'localFile' of null
at IndexPage (C:\Users\mig\Desktop\Gatsby\mf20\public\webpack:\lib\src\pages\index.js:164:62)
at processChild (C:\Users\mig\Desktop\Gatsby\mf20\node_modules\react-dom\cjs\react-dom-server.node.development.js:3043:14)
at resolve (C:\Users\mig\Desktop\Gatsby\mf20\node_modules\react-dom\cjs\react-dom-server.node.development.js:2960:5)
at ReactDOMServerRenderer.render (C:\Users\mig\Desktop\Gatsby\mf20\node_modules\react-dom\cjs\react-dom-server.node.development.js:3435:22)
at ReactDOMServerRenderer.read (C:\Users\mig\Desktop\Gatsby\mf20\node_modules\react-dom\cjs\react-dom-server.node.development.js:3373:29)
at renderToString (C:\Users\mig\Desktop\Gatsby\mf20\node_modules\react-dom\cjs\react-dom-server.node.development.js:3988:27)
at generateBodyHTML (C:\Users\mig\Desktop\Gatsby\mf20\public\webpack:\lib\.cache\ssr-develop-static-entry.js:259:34)
at Module../.cache/ssr-develop-static-entry.js.__webpack_exports__.default (C:\Users\mig\Desktop\Gatsby\mf20\public\webpack:\lib\.cache\ssr-develop-static-entry.js:289:19)
at C:\Users\mig\Desktop\Gatsby\mf20\node_modules\gatsby\src\utils\dev-ssr\render-dev-html-child.js:138:9
warn The path "/" errored during SSR.
Edit its component undefined to resolve the error.
My gatsby-node file:
exports.createPages = async ({ actions, graphql, reporter }) => {
const result = await graphql(`
{
allWpPage {
nodes {
__typename
id
databaseId
uri
}
}
}
`)
if (result.errors) {
reporter.error("There was an error fetching pages", result.errors)
}
const { allWpPage } = result.data
const pageTemplate = require.resolve(`./src/templates/page/WpPage.js`)
if (allWpPage.nodes.length) {
allWpPage.nodes.map(page => {
actions.createPage({
path: page.uri,
component: pageTemplate,
context: page,
})
})
}
const result2 = await graphql(`
{
allWpPost {
nodes {
__typename
id
databaseId
uri
}
}
}
`)
if (result2.errors) {
reporter.error("There was an error fetching posts", result.errors)
}
const { allWpPost } = result2.data
const postTemplate = require.resolve(`./src/templates/post/WpPost.js`)
if (allWpPost.nodes.length) {
allWpPost.nodes.map(post => {
actions.createPage({
path: post.uri,
component: postTemplate,
context: post,
})
})
}
}
Almost had it! I had to use isPostsPage and isFrontPage as written in OP. I could then make it skip frontpage and postpage by using eg !node.isfrontPage. My final gatsby-node which works and solves the issue:
exports.createPages = async ({ actions, graphql, reporter }) => {
const result = await graphql(`
{
allWpPage {
nodes {
__typename
id
databaseId
uri
isFrontPage
isPostsPage
}
}
}
`)
if (result.errors) {
reporter.error("There was an error fetching pages", result.errors)
}
const { allWpPage } = result.data
const pageTemplate = require.resolve(`./src/templates/page/WpPage.js`)
if (allWpPage.nodes.length) {
allWpPage.nodes
.filter(node => !node.isFrontPage || !node.isPostsPage)
.map(page => {
actions.createPage({
path: page.uri,
component: pageTemplate,
context: page,
})
})
}
const result2 = await graphql(`
{
allWpPost {
nodes {
__typename
id
databaseId
uri
}
}
}
`)
if (result2.errors) {
reporter.error("There was an error fetching posts", result.errors)
}
const { allWpPost } = result2.data
const postTemplate = require.resolve(`./src/templates/post/WpPost.js`)
if (allWpPost.nodes.length) {
allWpPost.nodes.map(post => {
actions.createPage({
path: post.uri,
component: postTemplate,
context: post,
})
})
}
}
Edit solution two - Probably better solution:
It is possible to filter for isFrontPage and isPostsPage directly using graphql query filters. Something like this:
query MyQuery {
allWpPage(filter: {isFrontPage: {eq: false}, isPostsPage: {eq: false}}) {
nodes {
isPostsPage
isFrontPage
uri
title
}
}
}
Updated and final gatsby-node file:
exports.createPages = async ({ actions, graphql, reporter }) => {
const result = await graphql(`
{
allWpPage(
filter: { isFrontPage: { eq: false }, isPostsPage: { eq: false } }
) {
nodes {
__typename
id
databaseId
uri
}
}
}
`)
if (result.errors) {
reporter.error("There was an error fetching pages", result.errors)
}
const { allWpPage } = result.data
const pageTemplate = require.resolve(`./src/templates/page/WpPage.js`)
if (allWpPage.nodes.length) {
allWpPage.nodes.map(page => {
actions.createPage({
path: page.uri,
component: pageTemplate,
context: page,
})
})
}
const result2 = await graphql(`
{
allWpPost {
nodes {
__typename
id
databaseId
uri
}
}
}
`)
if (result2.errors) {
reporter.error("There was an error fetching posts", result.errors)
}
const { allWpPost } = result2.data
const postTemplate = require.resolve(`./src/templates/post/WpPost.js`)
if (allWpPost.nodes.length) {
allWpPost.nodes.map(post => {
actions.createPage({
path: post.uri,
component: postTemplate,
context: post,
})
})
}
}

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,
},
})
})
}

Multiple templates in Gatsby.js

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,
},
})
})

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