Gatsby MDX showing title and slug but doesn't find page - javascript

I'm learning Gatsby and I wanted to use MDX for blog pages. I followed the tutorial here to programmatically create pages.
I can see them in my GraphQL, and displaying the list of all the articles is working with their title and slug, but when I click on the link to open them or type their address I have a 404 page. Do you have an idea from where it can come from?
This is my code:
gatsby-node.js:
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === "Mdx") {
const value = createFilePath({ node, getNode })
createNodeField({
name: "slug",
node,
value: `/blog${value}`,
})
}
}
gatsby-config.js:
plugins: [
{
resolve: `gatsby-plugin-mdx`,
options: {
defaultLayouts: { default: path.resolve('./src/layouts/post.js') },
extensions: [`.mdx`, `.md`],
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/posts`
}
},
],
index.js (with the list):
<ul>
{posts.map(({ node: post }) => (
<li key={post.id}>
<Link to={post.fields.slug}>
<h2>{post.frontmatter.title}</h2>
</Link>
<p>{post.excerpt}</p>
</li>
))}
</ul>
Query in index.js
export const pageQuery = graphql`
query blogIndex {
allMdx {
edges {
node {
id
excerpt
frontmatter {
title
}
fields {
slug
}
}
}
}
}
`
The template:
<div>
<div className="content" dangerouslySetInnerHTML={{ __html: post.html }}></div>
</div>
And the query for the template:
export const pageQuery = graphql`
query BlogPostQuery($id: String) {
mdx(id: { eq: $id }) {
id
body
frontmatter {
title
}
}
}
`
I made a repo if you want to test it: https://github.com/JulSeb42/gatsby-mdx
Thanks for your answers!

You are missing the page creation part. In your gatsby-node.js add the following:
const path = require("path")
exports.createPages = async ({ graphql, actions, reporter }) => {
// Destructure the createPage function from the actions object
const { createPage } = actions
const result = await graphql(`
query {
allMdx {
edges {
node {
id
fields {
slug
}
}
}
}
}
`)
if (result.errors) {
reporter.panicOnBuild('🚨 ERROR: Loading "createPages" query')
}
// Create blog post pages.
const posts = result.data.allMdx.edges
// you'll call `createPage` for each result
posts.forEach(({ node }, index) => {
createPage({
// This is the slug you created before
// (or `node.frontmatter.slug`)
path: node.fields.slug,
// This component will wrap our MDX content
component: path.resolve(`./src/components/posts-page-layout.js`),
// You can use the values in this context in
// our page layout component
context: { id: node.id },
})
})
}
Regarding your other issue, it should be a separate question but, if a component is exported as default, you don't need the curly braces when importing it. In addition, you need to return a value.

Related

Dynamic table of contents won't deploy on Vercel (... but works on localhost)

I'm trying to add a dynamic table of contents to my blogpage in next.js.
The code is running perfectly on my localhost, but as soon as I'm deploying it to vercel I got this error:
TypeError: Cannot read properties of undefined (reading 'content')
at BlogPost (/vercel/path0/.next/server/pages/posts/[slug].js:111:23)
at Jc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:64:191)
at Mc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:66:253)
at Z (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:71:89)
at Nc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:73:98)
at Mc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:67:131)
at Z (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:71:89)
at Mc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:70:13)
at Z (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:71:89)
at Nc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:73:98)
I found out that the build failure is produced by the command .processSync on line 85 (I wrote a comment there). Sadly I'm unable to fix this...
Any suggestions and help why this happens?
Here is the full code:
( I delete the grahpcms route when creating the GraphQLClient for safety, so that's not the failure here.)
import { GraphQLClient, gql } from "graphql-request";
import { useRouter } from "next/router";
import { unified } from "unified";
import rehypeParse from "rehype-parse/lib";
import rehypeStringify from "rehype-stringify/lib";
import { visit } from "unist-util-visit";
import parameterize from "parameterize";
const graphcms = new GraphQLClient();
const QUERY = gql`
query Post($slug: String!) {
post(where: { slug: $slug }) {
title
id
content {
html
}
datePublish
coverPhoto {
url
}
datePublish
}
}
`;
const SLUGLIST = gql`
{
posts {
slug
}
}
`;
export async function getStaticPaths() {
const { posts } = await graphcms.request(SLUGLIST);
return {
paths: posts.map((post) => ({ params: { slug: post.slug } })),
fallback: true,
};
}
export async function getStaticProps({ params }) {
const slug = params.slug;
const data = await graphcms.request(QUERY, { slug });
const post = data.post;
return {
props: {
post,
},
};
}
export default function BlogPost({ post }) {
const router = useRouter();
var toc = [];
//Forms the HTML String into a tree that we can add logic too
//Then forms that tree back into html string
const newContent = unified()
.use(rehypeParse, {
fragment: true,
})
.use(() => {
return (tree) => {
visit(tree, "element", (node) => {
if (node.tagName === "h2") {
const id = parameterize(node.children[0].value);
node.properties.id = id;
toc.push({
id: node.properties.id,
title: node.children[0].value,
});
console.log("id", id);
}
});
};
})
.use(rehypeStringify)
//THIS IS WHERE THE DELPLOYMENT FAILS
.processSync(post.content.html)
.toString();
if (router.isFallback) {
return <h2>Loading</h2>;
}
return (
<div>
<header>
<h1>{post.title}</h1>
<img
src={post.coverPhoto.url}
width="100%"
style={{ borderRadius: "1rem" }}></img>
<span>Published: {post.datePublish}</span>
</header>
<main>
<div>
{toc.map(({ id, title }) => {
return (
<li style={{ listStyle: "none" }} key={id}>
<a style={{ fontSize: "1.1rem" }} href={`#${id}`}>
<b> {title}</b>
</a>
</li>
);
})}
</div>
<div
className="blogpost"
dangerouslySetInnerHTML={{ __html: newContent }}
/>
</main>
</div>
);
}
Thank you very much!
I would try to use operator of optional changing like this:
post?.content?.html
Second step is to build project on your computer not to wait for building on vercel and detect another error.
P. S. You can handle undefined props by if statement but only don't forget to place it before main return statement and after all hooks.

Gatsby Develop: cannot destructure property `frontmatter` of undefined

I'm new to Gatsby and GraphQL. I was trying to build a blog-like website. I'm using a template for the blog post page which leads to the use of gatsby-node.js and I think this is causing the issue. It runs fine in development mode but as I try to build it, it gives me an error. I'm on the latest version of everything.
Command: Gatsby build
Error:
Also, here are my files attached.
gatsby-node.js
exports.createPages = async ({ actions, graphql, reporter, ...props }) => {
console.log(actions, graphql, reporter, props)
const { createPage } = actions
const blogPostTemplate = path.resolve(`src/pages/lead-generation.js`)
const result = await graphql(`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
frontmatter {
path
}
}
}
}
}
`)
// Handle errors
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.frontmatter.path,
component: blogPostTemplate,
context: {}, // additional data can be passed via context
})
})
}
lead-generation.js
export default class Template extends React.Component {
render() {
const { markdownRemark: { frontmatter = {}, html } = {} } =
this.props.data;
return (
<div className="p-3">
<div>
<h1>{frontmatter.title}</h1>
<h6 className="pb-5">{frontmatter.articleHeading}</h6>
<div
className="blog-post-content"
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>
<div
id="container"
className="p-5"
style={{ height: 500, width: 400 }}
>
{/* widget*/}
</div>
</div>
)
}
}
export const pageQuery = graphql`
query($path: String!) {
markdownRemark(frontmatter: { path: { eq: $path } }) {
html
frontmatter {
title
articleHeading
categoryID
}
}
}
`
markdown-file.md
---
path: "/works/remaining-URL"
title: "some title?"
articleHeading: "This is some heading"
categoryID: 10056
---
This is the further text
I have simplified the question out. Please let me know if you need further elaboration.

Building a Wordpress blog archive: filtering by date with GatsbyJS

Currently struggling with building an archive for a Wordpress blog using Gatsby. I'm trying to set it up where you navigate to a url like "localhost:8000/blog/11-19" and it shows all of the blog posts for November 2019. Right now I DO have the site set up where the url is created successfully based off the date, but when I try to loop through the query and actually generate the blog content, nothing appears. My gatsby-node file:
const path = require('path')
module.exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const blogPostTemplate = path.resolve('./src/templates/blog-post.js')
const blogCategoryFilter = path.resolve('./src/templates/blog-filter-category.js')
const blogArchiveFilter = path.resolve('./src/templates/blog-filter-archive.js')
const res = await graphql(`
query {
allWordpressPost {
edges {
node {
slug
date(formatString:"MM-YY")
}
}
}
allWordpressCategory {
edges {
node {
slug
}
}
}
}
`)
res.data.allWordpressPost.edges.forEach((edge) => {
createPage({
component: blogPostTemplate,
path: `/blog/${edge.node.slug}`,
context: {
slug: edge.node.slug,
}
})
})
res.data.allWordpressPost.edges.forEach((edge) => {
createPage({
component: blogArchiveFilter,
path: `/blog/${edge.node.date}`,
context: {
slug: edge.node.date,
}
})
})
res.data.allWordpressCategory.edges.forEach((edge) => {
createPage({
component: blogCategoryFilter,
path: `/blog/category/${edge.node.slug}`,
context: {
slug: edge.node.slug,
}
})
})
}
My template file (in this example, that's blogArchiveFilter--blog-filter-archive.js):
import React from 'react'
import { graphql, Link } from 'gatsby'
import Layout from '../components/layout'
import BlogNav from '../components/blognav'
import blogStyles from '../components/modules/blog.module.css'
export const query = graphql`
query($slug: Date!) {
allWordpressPost (filter: { date: { eq: $slug }}) {
edges {
node {
title
slug
content
date(formatString: "MMMM DD, YYYY")
}
}
}
}
`
export default ({ data }) => {
return (
<Layout>
<div className={blogStyles.blog_container}>
<div className={blogStyles.blogContent_container}>
<ol>
{data.allWordpressPost.edges.map((edge) => {
return (
<div className={blogStyles.blogPost_container}>
<li className={blogStyles.blog_list}>
<h2><Link to={`/blog/${edge.node.slug}`} className={blogStyles.blog_title} dangerouslySetInnerHTML={{ __html: edge.node.title }}></Link></h2>
<p className={blogStyles.blog_date}>{edge.node.date}</p>
<p className={blogStyles.blog_content} dangerouslySetInnerHTML={{ __html: edge.node.content }} />
</li>
</div>
)
})}
</ol>
</div>
<BlogNav />
</div>
</Layout>
)
}
EDIT:
Is there a way that I can set a variable within a query to change in response to a slug? For example: if the URL was "localhost:8000/blog/2019-11", I could filter gt: "$slug" and filter lt "2019-12". If the URL was "localhost:8000/blog/2019-10" I could filter gt: "$slug" and filter lt "2019-11". This would always generate the posts for the month and year in my URL. Example is below.
It's like if the slug is "2019-(a)", then the lt value would always be "2019-(a+1)". Can that be done?
query($slug: Date!){
allWordpressPost (filter: { date: { gt: $slug, lt: "2019-12" }}) {
edges {
node {
date(formatString: "MMMM DD, YYYY")
}
}
}
}

Filtering Wordpress categories in Gatsby

I'm currently creating a website using Gatsby and querying data from Wordpress using the gatsby-source-wordpress plug-in. In my gatsby-node file I've set things up to dynamically create web pages based on a post's category. When I try to run a query sorting by category slug and filtering by post date, I receive an error: "Error: The result of this StaticQuery could not be fetched."
Can someone take a look at this and let me know where I'm going wrong exactly?
The gatsby-node file:
const path = require('path')
module.exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const blogPostTemplate = path.resolve('./src/templates/blog-post.js')
const blogCategoryFilter = path.resolve('./src/templates/blog-filter-category.js')
const res = await graphql(`
query {
allWordpressPost {
edges {
node {
slug
}
}
}
allWordpressCategory {
edges {
node {
slug
}
}
}
}
`)
res.data.allWordpressPost.edges.forEach((edge) => {
createPage({
component: blogPostTemplate,
path: `/blog/${edge.node.slug}`,
context: {
slug: edge.node.slug,
}
})
})
res.data.allWordpressCategory.edges.forEach((edge) => {
createPage({
component: blogCategoryFilter,
path: `/blog/${edge.node.slug}`,
context: {
slug: edge.node.slug,
}
})
})
}
The template file I'm using to actually generate the filtered and sorted content (blog-filter-category.js):
import React from "react"
import Layout from '../components/layout'
import { Link, graphql, useStaticQuery } from 'gatsby'
import BlogNav from '../components/blognav'
import blogStyles from '../components/modules/blog.module.css'
const BlogPage = () => {
const data = useStaticQuery(graphql`
query($slug: String!) {
allWordpressCategory (filter: { slug: { eq: $slug } }) {
edges {
node {
name
}
}
}
allWordpressPost (sort: {fields:date, order:DESC}) {
edges {
node {
title
slug
content
date(formatString: "MMMM DD, YYYY")
}
}
}
}
`)
return (
<Layout>
<div className={blogStyles.blog_container}>
<div className={blogStyles.blogContent_container}>
<ol>
{data.allWordpressPost.edges.map((edge) => {
return (
<div className={blogStyles.blogPost_container}>
<li className={blogStyles.blog_list}>
<h2><Link to={`/blog/${edge.node.slug}`} className={blogStyles.blog_title} dangerouslySetInnerHTML={{ __html: edge.node.title }}></Link></h2>
<p className={blogStyles.blog_date}>{edge.node.date}</p>
<p className={blogStyles.blog_content} dangerouslySetInnerHTML={{ __html: edge.node.content }} />
</li>
</div>
)
})}
</ol>
</div>
<BlogNav />
</div>
</Layout>
)
}
export default BlogPage
I also tried querying this instead in my blog-filter-category.js file:
query($slug: String!) {
allWordpressPost (filter: {categories: {elemMatch: {slug: { eq: $slug }}}}) {
edges {
node {
title
slug
content
date(formatString: "MMMM DD, YYYY")
}
}
}
}
It seemed closer but I ended up netting the same error message. I'm lost! Thanks in advance for your help.
Congrats on you choice to build with Gatsby.
First problem I see is that you try to use variables in you static queries. This is not possible at the moment. Variable passed to page context are only available in page queries.
Page queries are defined in page templates as simple exports like:
import { graphql } from 'gatsby'
export const query = graphql`
query MyQuery($slug: String!) {
allWordpressPost (filter: {categories: {elemMatch: {slug: { eq: $slug }}}}) {
edges {
node {
title
slug
content
date(formatString: "MMMM DD, YYYY")
}
}
}
}`
Learn more about differences between page and static queries in Gatsby here:
https://www.gatsbyjs.org/docs/static-query/#how-staticquery-differs-from-page-query

Relay Error when deleting: RelayMutationQuery: Invalid field name on fat query

I'm running into an issue when I attempt to commit a deletion mutation. When I commit, I get the error Uncaught Invariant Violation: RelayMutationQuery: Invalid field name on fat query, `company`.. Viewing, creating and updating nodes all work. For some reason I just can't delete. It mentions the company field in the fatQuery, but the only field I have in the fat query is the deletedUserId I get back from the server. Thanks in advance!
Component:
import React, {Component} from 'react';
import Relay from 'react-relay';
import {Link} from 'react-router';
import DeleteUserMutation from 'mutations/DeleteUserMutation';
import styles from './EmployeeItem.css';
class EmployeeItem extends Component {
render() {
const {user} = this.props;
return (
<div className={styles.employee}>
<p><strong>ID:</strong> {user.id}</p>
<p><strong>First Name:</strong> {user.firstName}</p>
<p><strong>Last Name:</strong> {user.lastName}</p>
<p><strong>Email:</strong> {user.email}</p>
<div className="btn-group">
<Link to={`/company/employees/${user.id}`} className="btn btn-primary">View Employee</Link>
<button onClick={this.handleRemove} className="btn btn-danger">Delete User</button>
</div>
</div>
)
}
handleRemove = (e) => {
e.preventDefault();
const {user, company} = this.props;
Relay.Store.commitUpdate(new DeleteUserMutation({user, company}));
};
}
export default Relay.createContainer(EmployeeItem, {
fragments: {
company: () => Relay.QL`
fragment on Company {
id
${DeleteUserMutation.getFragment('company')}
}
`,
user: () => Relay.QL`
fragment on User {
id
firstName
lastName
email
${DeleteUserMutation.getFragment('user')}
}
`
}
});
Mutation:
import React from 'react';
import Relay from 'react-relay';
export default class DeleteUserMutation extends Relay.Mutation {
static fragments = {
company: () => Relay.QL`
fragment on Company {
id
}
`,
user: () => Relay.QL`
fragment on User {
id
}
`
};
getMutation() {
return Relay.QL`mutation {deleteUser}`;
}
getFatQuery() {
return Relay.QL`
fragment on DeleteUserPayload {
deletedUserId
}
`;
}
getVariables() {
return {
id: this.props.user.id,
}
}
getConfigs() {
return [{
type: 'NODE_DELETE',
parentName: 'company',
parentID: this.props.company.id,
connectionName: 'employees',
deletedIDFieldName: 'deletedUserId'
}]
}
// Wasn't sure if this was causing the error but it appears to be
// something else.
// getOptimisticResponse() {
// return {
// deletedUserId: this.props.user.id
// }
// }
}
This error is referring to the fact that you reference the "company" in your getConfigs() implementation. The NODE_DELETE config tells Relay how to construct the mutation query by mapping nodes in the store (e.g. parentID) to fields on the fat query (e.g. parentName).
Although you might not necessarily need it today, you should add the company to the mutation payload & fat query here, since the company is being affected by this change. More specifically, the company's employees connection is being modified :)
NevilleS' solution solved it for me:
I added a globalId to the root field (in my case an object called "verify") and I also changed my mutation on the server to return an edge, rather than just the underlying type. I also added the root "verify" object to the mutation output fields: it would make sense that the client's relay mutation needs that to know which object owns the connection, where to put the new edge.
export const Verify = new GraphQLObjectType({
name: 'Verify',
fields: () => ({
id: globalIdField('Verify'),
verifications: {
args: connectionArgs,
type: VerificationConnection,
resolve: (rootValue, args) => connectionFromArray(rootValue.verifications, args)
},
Adding "verify" and "verificationEdge" to the mutation's output fields.
export const AddVerifiedSchool = mutationWithClientMutationId({
name: 'AddVerifiedSchool',
inputFields: {
verification: {
type: VerifiedSchoolInput
}
},
outputFields: {
success: {
type: GraphQLBoolean,
resolve: () => true
},
verificationEdge: {
type: VerificationEdge,
resolve: ({verification, context}) => {
console.log('verification', verification);
return verification
}
},
verify: {
type: Verify,
resolve: ({verification, context}) => {
return context.rootValue
}
}
},
Adding the verify field to the fat query, and (the globalId "id" from verify) to the fragments, and using the new globalId to identify the node where the connection exists.
static fragments = {
verify: () => Relay.QL`fragment on Verify { id }`,
action: () => Relay.QL`fragment on Action { name url }`
};
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'verify',
parentID: this.props.verify.id,
connectionName: 'verifications',
edgeName: 'verificationEdge',
rangeBehaviors: {
'': 'append'
}
}];
}
getFatQuery() {
return Relay.QL`
fragment on AddVerifiedSchoolPayload {
verification {
${VerifiedSchool.getFragment('verification')}
}
verify {
id
}
}`
}

Categories

Resources