Getting an error when building site even though Gatsby develop works fine, the markdown is coming from Netlify CMS. The specific error is:
error Building static HTML failed for path "/null"
See our docs page on debugging HTML builds for help https://gatsby.app/debug-html
11 | export default function Template({ data }) {
12 | const { markdownRemark } = data;
> 13 | const { frontmatter, html } = markdownRemark;
| ^
14 | return (
15 | <Layout>
16 | <SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
WebpackError: TypeError: Cannot read property 'frontmatter' of null
- blogTemplate.js:13 Template
lib/src/templates/blogTemplate.js:13:11
I have been reading the other similar paths and have tried changing the path in my markdown files from the cms but it was to no avail.
gatsby-config.js
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/blog`,
name: "markdown-pages"
}
},
blogTemplate.js
export default function Template({ data }) {
const { markdownRemark } = data;
const { frontmatter, html } = markdownRemark;
return (
<Layout>
<SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
<div className="blog-posts">
<h1 className="blog-title">{frontmatter.title}</h1>
<h2 className="blog-date">{frontmatter.date}</h2>
<div
className="blog-post-content"
dangerouslySetInnerHTML={{ __html: html }}
/>
<Link to="/blog/" className="backBTN">
Go Back
</Link>
</div>
<Footer />
</Layout>
);
}
export const pageQuery = graphql`
query($path: String!) {
markdownRemark(frontmatter: { path: { eq: $path } }) {
html
frontmatter {
date(formatString: "MMMM DD, YYYY")
path
title
}
}
}
`;
gatsby-node.js
const path = require("path");
exports.createPages = ({ actions, graphql }) => {
const { createPage } = actions;
const blogPostTemplate = path.resolve(`src/templates/blogTemplate.js`);
return graphql(`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 1000
) {
edges {
node {
frontmatter {
path
}
}
}
}
}
`).then(result => {
if (result.errors) {
return Promise.reject(result.errors);
}
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: `${node.frontmatter.path}`,
component: blogPostTemplate,
context: {}
});
});
});
};
GraphQL
the query returns the following error unless I pass in a specific path of one of the blog posts then it returns the correct results
{
"errors": [
{
"message": "Variable \"$path\" of required type \"String!\" was not provided.",
"locations": [
{
"line": 1,
"column": 7
}
]
}
]
}
graphql screenshot with variable passed in
I think that is all the relevant code that is needed here, just let me know if anything else is needed.
Check out this very first line of the error:
error Building static HTML failed for path "/null"
I bet one of your .md has an empty path. Maybe you can try filtering to find a markdown node with empty frontmatter path, or null?
If that path was coming directly from netlifyCMS & you use the string widget, IIRC you can pass in a default options so it'll fallback to that
Related
I am deploying a Gatsby site using mdx files. When I use npm run develop, my site works as expected. When I use npm run build, I encounter the following error:
Variable "$slug" of required type "String!" was not provided.
It points to my blog.js file (Url path: /templates/blog/) as the source of the error. After some imports, blog.js appears as follows:
export const query = graphql`
query (
$slug: String!
) {
mdx(
fields: {
slug: {
eq: $slug
}
}
) {
frontmatter {
title
details
date(formatString: "LL")
tags
}
body
}
}`
export default function Blog(props) {
return (...content...
)
}
The relevant parts of my config file is as you would expect.
module.exports = {
siteMetadata: {
title: '...',
author: '...'
},
plugins: [...,
{resolve: 'gatsby-source-filesystem',
options: {
name:'src',
path: `${__dirname}/src/`
}
...
My gatsby-node.js is as follows:
/* pathing via node.js; for path.basename*/
const path = require('path')
/* node function that runs when node is created*/
module.exports.onCreateNode = ({node, actions}) => {
const {createNodeField} = actions
if (node.internal.type === `Mdx`) {
const slug = path.basename(node.fileAbsolutePath, '.mdx')
createNodeField({
node,
name: 'slug',
value: slug
})
}
}
// Creating blog pages
// 1.Get path to template
// 2.Get markdown data
// 3.create new pages
module.exports.createPages = async ({graphql, actions}) => {
const{ createPage } = actions
const blogTemplate = path.resolve('./src/templates/blog.js')
const res = await graphql(`
query {
allMdx {
edges {
node {
fields {
slug
}
}
}
}
}
`)
res.data.allMdx.edges.forEach((edge) => {
createPage({
component: blogTemplate,
path: `/blog/${edge.node.fields.slug}`,
context: {
slug: edge.node.fields.slug
}
})
})
}
I'm quite perplexed, as I can open graphQL and find exactly what I would expect at each stage. What am I doing wrong for the build process to fail yet develop process to work?
It's an odd issue, and it would need a careful debug to know what is happening.
My guess is that in some post, the slug is not properly defined, so it's not matching the String! condition, which means that the slug will be a string (everything ok until here) but non-nullable (because of the exclamation mark, !. Further reference here) so it's breaking your GraphQL query.
Try using it as a nullable field:
export const query = graphql`
query (
$slug: String
) {
mdx(
fields: {
slug: {
eq: $slug
}
}
) {
frontmatter {
title
details
date(formatString: "LL")
tags
}
body
}
}`
Your gatsby-node.js looks good so far.
As I mentioned, the ideal solution would be debugging each post to know if there's any invalid slug (adding a debugger or a console.log and checking its values in the terminal's console).
I'm making an SSR with GraphQL and Next.js but I have a problem when I try to map some data.
I'm bringing some data from Github GraphQL API (Followers, Following, Public Repos) and when I pass this data to the code all my data works fine. But when I try to map my Showcase Repos to print on screen, the problems starts, anyone can help me?
My stack at this moment is GraphQL (GraphQl-request), Next.js, styled-components and TypeScript.
This is my query:
fragment totalFollowing on User {
following(first: 1) {
totalCount
}
}
fragment totalFollowers on User {
followers(first: 1) {
totalCount
}
}
fragment totalRepositories on User {
repositories(affiliations: OWNER, first: 1) {
totalCount
}
}
fragment showcase on User {
itemShowcase {
items(first: 10) {
nodes {
... on Repository {
name
description
url
}
}
}
}
}
query GET_GITHUB_QUERIES {
viewer {
...totalFollowing
...totalFollowers
...totalRepositories
...showcase
}
}
and this is my types at moment:
export type FollowingProps = {
totalCount: number
}
export type FollowersProps = {
totalCount: number
}
export type RepositoriesProps = {
totalCount: number
}
export type itemShowCaseProps = {
itemShowcase: {
items: {
nodes: [
{
name: string
description: string
url: string
}
]
}
}
}
export type QueryProps = {
following: FollowingProps
followers: FollowersProps
repositories: RepositoriesProps
itemShowcase: itemShowCaseProps
}
My call on index.tsx
export const getStaticProps: GetStaticProps = async () => {
const { viewer } = await client.request(query)
return {
props: {
...viewer
}
}
}
Still on my index.tsx, i have this component where i need to pass the data:
<S.MyWorkSection id="my-work" data-aos="fade-up">
<MyWorkGithub
repositories={repositories}
following={following}
followers={followers}
{...{ itemShowcase }}
/>
</S.MyWorkSection>
And finaly this is my component:
const MyWorkGithub: React.FC<QueryProps> = ({
following,
followers,
repositories,
itemShowcase
}) => (
<>
<Title text="Works" icon={true} />
<S.HighLightWrapper>
<HighLightCard
hlNumber={repositories.totalCount}
hlDescription={'repositories'}
/>
<HighLightCard
hlNumber={followers.totalCount}
hlDescription={'followers'}
/>
<HighLightCard
hlNumber={following.totalCount}
hlDescription={'following'}
/>
</S.HighLightWrapper>
<S.CardWrapper>
{itemShowcase.itemShowcase.items.nodes.map((repo, index) => (
<CardComponent
key={index}
url={repo.url}
name={repo.name}
description={repo.description}
/>
))}
</S.CardWrapper>
</>
)
this is my error:
TypeError: cannot read property 'items' of undefined
<SCardWrapper>
{itemShowcase.itemShowcase.items.node.map((repo, index) => (
...
https://i.imgur.com/LzOmb2z.png
Other datas like repositories, followers and following still works fine.
Edit: When I put on Github Explorer, all data is fine.
I'm confused as to why, in local development running gatsby build && gatsby serve on localhost:9000, I'm able to programmatically create category pages that query and output a list of posts based on a category slug variable but when I deploy the site to Netlify, the category pages show zero results, regardless of category or how many posts match the query.
// gatsby.node
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const categoryTemplate = path.resolve('src/templates/categoryTemp.js')
const result = await graphql(`
query {
allWordpressCategory {
edges {
node {
id
path
slug
name
description
}
}
}
}
`)
const { allWordpressCategory } = result.data
allWordpressCategory.edges.forEach(({ node }) => {
if (node.slug === 'portfolio') return
createPage({
path: node.path,
component: categoryTemplate,
context: {
id: node.id,
slug: node.slug,
name: node.name,
description: node.description,
url: node.path
}
})
})
That context property in the createPage action, containing the slug for the category, is passed to the categoryTemplate page/component I've set up (categoryTemp.js).
The slug is used in the GraphQL query as a filtering variable and should return all the posts the match that category:
// src/templates/categoryTemp.js
import React from "react"
import { graphql, Link } from "gatsby"
import Layout from "../components/layout/layout"
import PageContent from "../components/layout/pageContent"
export default ({ data, pageContext }) => {
const { allWordpressPost } = data
return (
<Layout path={pageContext.url} layoutClass={"category"}>
<PageContent>
<h2>{allWordpressPost.totalCount} posts in category: {pageContext.name}</h2>
<ul>
{allWordpressPost.edges.map(({ node }, idx ) => (
<li key={idx}><Link to={node.path} title={node.title}>{node.title}</Link></li>
))}
</ul>
</PageContent>
</Layout>
)
}
export const categoryQuery = graphql`
query($slug: String!) {
allWordpressPost(filter: {categories: {elemMatch: {slug: {eq: $slug}}}}) {
edges {
node {
id
title
path
}
}
totalCount
}
}
`
As I said before, this works locally in development and when I run gatsby build && gatsby serve, which is usually a good production test to see how your gatsby site will fare on a server. However, the live site fails to return any query matches:
I'm wondering if there's perhaps some intermediary step that I need to take, like overriding the default context property when programmatically processing and creating these category templates but I'm afraid I'm a little doubtful I'll be able to figure this out promptly or at all.
Similarly, I'm seeing this pattern a lot: Complex query variables in GraphQL (via Gatsby) but need a nudge in how to implement it.
Does anyone have any ideas or suggestions?
// gatsby-config.js
module.exports = {
siteMetadata: {
...
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: "Peak Websites",
short_name: "Peak Websites",
start_url: "/",
background_color: "#27688E",
theme_color: "#27688E",
display: "minimal-ui",
icon: "src/images/logo/Logo_squared.png",
},
},
{
resolve: `gatsby-source-wordpress`,
options: {
baseUrl: `pathtomysite.com`,
protocol: `https`,
useACF: false,
verboseOutput: false,
hostingWPCOM: false
}
},
`gatsby-plugin-sass`,
{
resolve: `gatsby-plugin-prefetch-google-fonts`,
options: {
fonts: [{
family: `Montserrat`,
variants: [`300`,`300i`,`400`,`400i`,`700`]
}],
},
}, {
resolve: `gatsby-plugin-google-analytics`,
options: {
trackingId: process.env.GA_TRACKING_CODE,
head: false,
},
},
// `gatsby-plugin-sitemap`
// this (optional) plugin enables Progressive Web App + Offline functionality
// To learn more, visit: https://gatsby.dev/offline
// `gatsby-plugin-offline`,
],
}
Two adjustments I made to solve this problem.
First, I modified my query to the data layer under allWordpressPosts in my gatsby-node.js to query categories > slug, i.e:
allWordpressPost {
edges {
node {
id
wordpress_id
status
path
categories {
slug
}
}
}
}
Then I wrote a filter function inside the function component before rendering the list of matching posts.
const filteredPosts = allWordpressPost.edges.filter(({node}) => {
return node.categories.some(({ slug }) => slug === pageContext.slug)
})
return (
<Layout path={pageContext.url} layoutClass={"category"}>
<PageContent>
<h2>{allWordpressPost.totalCount} posts in category: {pageContext.name}</h2>
<ul>
{filteredPosts.map(({ node }, idx ) => (
<li key={idx}><Link to={node.path} title={S(node.title).decodeHTMLEntities().s}>{S(node.title).decodeHTMLEntities().s}</Link></li>
))}
</ul>
</PageContent>
</Layout>
)
pageContext is passed to each category page at build time and contains all the props, including slug that I defined in the createPages action:
createPage({
path: node.path,
component: categoryTemplate,
context: {
id: node.id,
slug: node.slug,
...
}
})
ERROR #11321 PLUGIN
"gatsby-node.js" threw an error while running the onCreateNode lifecycle:
Cannot read property 'replace' of undefined
25 | const { createNodeField } = actions;
26 | if (node.internal.type === 'MarkdownRemark') {
> 27 | const slug = createFilePath({ node, getNode, basePath: 'content' });
| ^
28 | createNodeField({
29 | node,
30 | name: 'slug',
File: gatsby-node.js:27:18
TypeError: Cannot read property 'replace' of undefined
- path.js:54 slash
[netcreative0]/[gatsby-core-utils]/dist/path.js:54:15
- create-file-path.js:41 module.exports
[netcreative0]/[gatsby-source-filesystem]/create-file-path.js:41:61
- gatsby-node.js:27 Object.exports.onCreateNode
C:/code/netcreative0/gatsby-node.js:27:18
- api-runner-node.js:247 runAPI
[netcreative0]/[gatsby]/dist/utils/api-runner-node.js:247:41
- api-runner-node.js:387 resolve
[netcreative0]/[gatsby]/dist/utils/api-runner-node.js:387:13
- new Promise
- api-runner-node.js:386 runPlugin
[netcreative0]/[gatsby]/dist/utils/api-runner-node.js:386:10
- api-runner-node.js:340 module.exports
[netcreative0]/[gatsby]/dist/utils/api-runner-node.js:340:24
- next_tick.js:68 process._tickCallback
internal/process/next_tick.js:68:7
Here's all the code in that file. I found this link https://github.com/datocms/gatsby-source-datocms/issues/52 but it didn't seem to help to remove getNode. in the path plugin, is this slash. I noticed that the path is undefined when I console.log it but I don't know what to do about it.
**function slash(path) {
const isExtendedLengthPath = /^\\\\\?\\/.test(path);
if (isExtendedLengthPath) {
return path;
}
return path.replace(/\\/g, `/`);
}**
const path = require('path');
const DirectoryNamedWebpackPlugin = require('directory-named-webpack-plugin');
const { createFilePath } = require('gatsby-source-filesystem');
exports.onCreateWebpackConfig = ({
stage,
getConfig,
rules,
loaders,
actions,
}) => {
actions.setWebpackConfig({
resolve: {
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
plugins: [
new DirectoryNamedWebpackPlugin({
exclude: /node_modules/,
}),
],
},
});
};
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === 'MarkdownRemark') {
const slug = createFilePath({ node, getNode, basePath: 'slugs' });
console.log(slug);
createNodeField({
node,
name: `slug`,
value: slug,
});
}
};
the second half of my code that looks like:
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
console.log(node)
createPage({
path: node.fields ? node.fields.slug : '',
component: path.resolve(`./src/template/project.js`),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: node.fields ? node.fields.slug : '',
},
})
});
}
The last field is null and throws an error. Not sure why it's even there? I could easily put in a condition but what would the default be? Any suggestions? See below
{ fields: { slug: '/about/content/' } }
{ fields: { slug: '/home/intro/' } }
{ fields: { slug: '/slugs/goldparent/' } }
{ fields: { slug: '/slugs/ipadmaster/' } }
{ fields: null }
After removing the contentful plugin, I was able to see a more useful error message.
Some images were missing from the .md files when creating new pages so an error appeared on this line in my template page
data.frontmatter.featuredImage.childImageSharp.fluid <-
I added a ternary condition to the image tag and it built after that
<Img
style={{ width: '70%' }}
fluid={
data.frontmatter.featuredImage
? data.frontmatter.featuredImage.childImageSharp.fluid
: {}
}
alt="Large Image"
/>
All the Gatsby starter demos have a path like /gatsby-starter-blog/hi-folks/
How do I set it up with /2015-05-28/hi-folks/ or just the year with /2015/hi-folks/.
Thanks!
Two options:
1) Just put the blog posts in directories named like you want the url to be so in this case /2015-05-28/hi-folks/index.md.
2) You can programmatically set paths by exporting a function from gatsby-node.js called rewritePath. It is called for each page with filesystem data for the file the page comes from + the page's metadata. So say you want to set the date of the post in your markdown's frontmatter and have each post be a simple markdown file with paths like /a-great-blog-post.md
So to do what you want, add to your gatsby-node.js something like:
import moment from 'moment'
exports.rewritePath = (parsedFilePath, metadata) => {
if (parsedFilePath.ext === "md") {
return `/${moment(metadata.createdAt).format('YYYY')}/${parsedFilePath.name}/`
}
}
rewritePath is no longer supported in Gatsby. Here's a solution confirmed to works in Gatsby 2.3,
const m = moment(node.frontmatter.date)
const value = `${m.format('YYYY')}/${m.format('MM')}/${slug}`
createNodeField({ node, name: 'slug', value })
In my gatsby-node.js file I added a slug generator and a template resolver.
Inside src/posts I added a folder 2020 inside that folder I create folders that make slug address paths like so my-blog-post and inside those folders, I have an index.md file.
Now I have URL's that look like http://www.example.com/2020/my-blog-post/.
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
const { createFilePath } = require(`gatsby-source-filesystem`);
// Generates pages for the blog posts
exports.createPages = async function ({ actions, graphql }) {
const { data } = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`);
data.allMarkdownRemark.edges.forEach((edge) => {
const slug = edge.node.fields.slug;
actions.createPage({
path: slug,
component: require.resolve(`./src/templates/blog-post.js`),
context: { slug: slug },
});
});
};
// Adds url slugs to the graph data response
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode, basePath: `posts` });
createNodeField({
node,
name: `slug`,
value: slug,
});
}
};
Inside my src/templates/blog-post.js file I have:
import React from 'react';
import { graphql } from 'gatsby';
import Layout from '../components/layout';
export default function BlogPost({ data }) {
const post = data.markdownRemark;
const tags = (post.frontmatter.tags || []).map((tag, i) => {
return <li key={i}>#{tag}</li>;
});
return (
<Layout>
<article className="post">
<header>
<h1>{post.frontmatter.title}</h1>
<div className="meta">
<time>{post.frontmatter.date}</time>
<ul>{tags}</ul>
</div>
</header>
<section dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
</Layout>
);
}
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
date(formatString: "DD-MMMM-YYYY")
tags
}
}
}
`;
My markdown files then use frontmatter data like so:
---
title: Post title here
date: "2020-05-25T14:23:23Z"
description: "A small intro here"
tags: ["tag1", "tag2"]
---
Main post content would be here...