gatsbyjs / gatsby

The best React-based framework with performance, scalability and security built in.
https://www.gatsbyjs.com
MIT License
55.23k stars 10.32k forks source link

improve documentation for using Gatsby with Markdown #4932

Closed tsiq-swyx closed 6 years ago

tsiq-swyx commented 6 years ago

note to future readers: original title of this issue was: Completely unable to debug graphql query variable when using gatsby-transformer-remark and createPages. This then changed once i solved it and we got to discussing better docs

first of all, i love gatsby, and please dont read my comments below as criticism! was just frustrated that i couldnt get this basic thing to work and i think we could talk abit about how to make the gatsby-to-graphql connection less "magical"

Description

Following the basic gatsby-transformer-remark blogpost instructions doesn't work. The immediate symptom is that My template pageQuery injects data: {markdownRemark: null} so of course i cannot render anything.

I suspect the root cause is that the query variable that is being passed to pageQuery is malformed somehow. even though I have tested it in GraphiQL and it works, there is a fundamentally undebuggable step where the wrong query variable is passed in and I can't simulate this case in GraphiQL because I have no idea what Gatsby is passing in as a query variable. How do I figure this out!

Steps to reproduce

clone https://github.com/tsiq-swyx/dsr-react and gatsby develop, hit http://localhost:8000/Polaris or any other page.

Notice that the page build process is completely fine, the error only happens when you try to view the page.

Expected result

It should render the page properly.

Actual result

as descrived above

TypeError: Cannot read property 'frontmatter' of null
new Template
src/templates/design-system-template.js:11
   8 | console.log('data', data)
   9 | return (
  10 |   <div className="design-system-container">
> 11 |     <Helmet title={`Design System: ${post.frontmatter.title}`} />
  12 |     <div className="design-system">
  13 |       <h1>{post.frontmatter.title}</h1>
  14 |       <div
View compiled
▶ 49 stack frames were collapsed.
This screen is visible only in development. It will not appear if the app crashes in production.
Open your browser’s developer console to further inspect this error.

Environment

"gatsby": "^1.9.247",
"gatsby-link": "^1.6.40",
"gatsby-plugin-react-helmet": "^2.0.10",
"gatsby-source-filesystem": "^1.5.29",
"gatsby-transformer-remark": "^1.7.39",
"react-helmet": "^5.2.0"

File contents (if changed):

gatsby-config.js:

module.exports = {
  siteMetadata: {
    title: 'Design Systems Repo',
  },
  plugins: [
    'gatsby-plugin-react-helmet',
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/data/_design-systems`,
        name: '_design-systems',
      },
    },
    `gatsby-transformer-remark`,
  ],
}

package.json:

{
  "name": "gatsby-starter-default",
  "description": "Gatsby default starter",
  "version": "1.0.0",
  "author": "Kyle Mathews <mathews.kyle@gmail.com>",
  "dependencies": {
    "gatsby": "^1.9.247",
    "gatsby-link": "^1.6.40",
    "gatsby-plugin-react-helmet": "^2.0.10",
    "gatsby-source-filesystem": "^1.5.29",
    "gatsby-transformer-remark": "^1.7.39",
    "react-helmet": "^5.2.0"
  },
  "keywords": [
    "gatsby"
  ],
  "license": "MIT",
  "scripts": {
    "build": "gatsby build",
    "develop": "gatsby develop",
    "format": "prettier --write 'src/**/*.js'",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "devDependencies": {
    "prettier": "^1.11.1"
  }
}

gatsby-node.js:

/**
 * Implement Gatsby's Node APIs in this file.
 *
 * See: https://www.gatsbyjs.org/docs/node-apis/
 */

// You can delete this file if you're not using it

const path = require('path')

exports.createPages = ({ boundActionCreators, graphql }) => {
  const { createPage } = boundActionCreators

  const DSTemplate = path.resolve(`src/templates/design-system-template.js`)
  return graphql(`
    {
      allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 1000
      ) {
        edges {
          node {
            frontmatter {
              title
            }
          }
        }
      }
    }
  `).then(result => {
    if (result.errors) {
      return Promise.reject(result.errors)
    }

    result.data.allMarkdownRemark.edges.forEach(({ node }) => {
      createPage({
        // path: slugify(node.frontmatter.title),
        path: node.frontmatter.title,
        component: DSTemplate,
        context: {}, // additional data can be passed via context
      })
    })
  })
}

function slugify(text) {
  return text
    .toString()
    .toLowerCase()
    .replace(/\s+/g, '-') // Replace spaces with -
    .replace(/[^\w\-]+/g, '') // Remove all non-word chars
    .replace(/\-\-+/g, '-') // Replace multiple - with single -
    .replace(/^-+/, '') // Trim - from start of text
    .replace(/-+$/, '') // Trim - from end of text
}

gatsby-browser.js: n/a gatsby-ssr.js: n/a design-system-template.js:

import React from 'react'
import Helmet from 'react-helmet'

export default function Template({
  data, // this prop will be injected by the GraphQL query we'll write in a bit
}) {
  const { markdownRemark: post } = data // data.markdownRemark holds our post data
  console.log('data', data)
  return (
    <div className="design-system-container">
      <Helmet title={`Design System: ${post.frontmatter.title}`} />
      <div className="design-system">
        <h1>{post.frontmatter.title}</h1>
        <div
          className="design-system-content"
          dangerouslySetInnerHTML={{ __html: post.html }}
        />
        <div>{JSON.stringify(post)}</div>
      </div>
    </div>
  )
}

// I suspect this $path variable here is getting the wrong value,
// hence the pageQuery is returning null. I have no way to debug this and this is probably a bad thing.
export const pageQuery = graphql`
  query DesignSystemsByPath($path: String) {
    markdownRemark(frontmatter: { title: { eq: $path } }) {
      html
      frontmatter {
        title
        date
        company
        link
        image
        description
      }
    }
  }
`
pieh commented 6 years ago

You can access context via props in your page component - this.props.pathContext - this will let you debug used variable and test that in graphiql

tsiq-swyx commented 6 years ago

ok thank you. and does gatsby do a special case for $path? because now I am wondering why i have to use context at all when the path variable is basically exactly for this purpose.

this is the Page query from the original blogpost:

image

and this is how i'm calling my page query from gatsby-node.js (uses a different variable since im using the $slug query variable, but the point still stands, i should be able to use $path without configuring context right?

image

pieh commented 6 years ago

Depending in how you structure your data context may or might not be needed. Some data might not have path, but have other unique identifier that could be use to select proper node

Yes, you should be able to use path there - but you would need to filter by fields.slug in your query then

KyleAMathews commented 6 years ago

Totally agree that we should add more debugging help for getting page queries working.

Could you try passing the markdown node id in context and using that to query in the page query? That's what most people should do so we should make sure our docs push that more. What was the blog post you're referencing here?

tsiq-swyx commented 6 years ago

yes at this point ive figured out the workaround and should write it up for a blogpost or a docs PR at some point.

@KyleAMathews this was the blogpost https://www.gatsbyjs.org/blog/2017-07-19-creating-a-blog-with-gatsby/

So at this point this Issue is just a Docs issue because ive been able to figure out my specific issue based on @pieh 's hint. nothing is inherently broken in gatsby and i could close this issue and carry on with life. but if you're interested in a docs discussion...

Consider the user path through the docs when trying to set up gatsby to consume markdown:

AND i'm someone who has used gatsby to set up a blog before (months ago).

I understand gatsby has bigger concerns than just consuming markdown. but i hope it's up there, i think this JAMstack idea has legs.

tsiq-swyx commented 6 years ago

for future readers who find this: this is how i ended up configuring my gatsby-node.js image

and you consume it with:

image

m-allanson commented 6 years ago

Thanks @tsiq-swyx, this is great feedback. Consuming markdown should definitely be a 'happy path' when using Gatsby.

cc'ing @shannonbux as I think you'll be interested in this.

KyleAMathews commented 6 years ago

re) docs — we try to push everyone to go through the tutorial for this stuff — these things are explained thoroughly there. Perhaps we should more liberally scatter through the docs links to the tutorial.

One problem Gatsby has re) markdown is that Gatsby wants to be a low-level framework rather than a very simple (and limited) high-level tool. We're working on a theme system design that'll let us build a "GatsbyPress" that'll have everything setup for you so you don't have to go through a lengthy learning process when you just want to create a React/markdown blog or content site.

tsiq-swyx commented 6 years ago

im fine with closing this issue if nothing needs to be done fundamentally.

shannonbux commented 6 years ago

@tsiq-swyx this is a great point because even though I assume the tutorial explains all this thoroughly (I'm not a developer so I'm deferring to @KyleAMathews), the information likely needs to be more discoverable or easy to find. Thank you for describing your user pathway when setting up Gatsby to consume Markdown.

Feel free to close this issue if you'd like; either way, I'm adding this issue to a list of "pain points" Gatsby users run into and would be happy to chat with you more to hear your experience and ideas! Go to calendly.com/shannon-soper to put an event on my calendar if you are willing and have the time to chat for 15 mins!

tsiq-swyx commented 6 years ago

i'll close it. i hope if people run into similar problems in future they can find my issue; hopefully they just won't. booked your calendly :)

billyjacoby commented 5 years ago

What was the solution for this problem?

I've followed the tutorials to try and render my blog posts using markdown, and that hasn't worked.

I stumbled upon this thread, and tried everything here too but it seems this is over a year old so a lot of this is deprecated...

jonniebigodes commented 5 years ago

@billyjacoby if you could share a reproduction of your issue following the steps in here i can take a look at it and try to help you out. Sounds good?

billyjacoby commented 5 years ago

I have setup a repo that contains a slimmed down version of the code that throws the same error in a repository here:

https://github.com/billyjacoby/gatsby-markdown-bug

This is just the gatsby-default-starter, then the code from the tutorials for adding netlify-cms: https://www.gatsbyjs.org/docs/sourcing-from-netlify-cms/

and how to use gastsby-plugin-source and react-transformer-remark to show content: https://www.gatsbyjs.org/docs/adding-markdown-pages/

Let me know if you need anything else!

jonniebigodes commented 5 years ago

@billyjacoby sorry for the delay in the response. Now i'm going to enumerate the steps and offer some considerations about this. Starting with the steps.

media_folder: static/assets public_folder: assets

collections:

Otherwise it would start to throw errors, that markdown-bug does not exist.

- Checked your `/static/blog/test-post.md` and made a small change to it. Namely:
```markdown
---
#path: test-post
path: /test-post
date: 2019-05-01T18:18:22.160Z
title: New Test Post
---

## New test post

something something

import Layout from "../components/layout"

import SEO from "../components/seo"

const IndexPage = ({ data }) => (

Hi people

Welcome to your new Gatsby site.

Now go build something great.

    { data.allMarkdownRemark.edges.map(item=>
  • {item.node.frontmatter.title}
  • ) }
  • Go to page 2

) export const query = graphql { allMarkdownRemark( sort: { order: DESC, fields: [frontmatter___date] } limit: 1000 ) { edges { node { frontmatter { path title } } } } } export default IndexPage

Issued `gatsby develop` opened up `http://localhost:8000` and i'm presented with the following:
![billie1](https://user-images.githubusercontent.com/22988955/57097057-9f3c2700-6d0e-11e9-9265-2fb369019125.png)

- Clicking `New Test Post`, yelded the following:
![billie2](https://user-images.githubusercontent.com/22988955/57097101-baa73200-6d0e-11e9-8a22-58b29e0b9584.png)

- Stopped the development server, issued `gatsby clean`, followed by `gatsby build && gatsby serve` to get a full production build. Applied the same testing and it yelded the same result.

Now for some considerations.
- The issue discussed here is a bit different, it's based on a way to fetch specific markdown items/pages, by a slug, or in other words, something that uniquely identifies the page. For your case it's not needed and i'm basing this on the reproduction you supplied.
- After reading the both documents, the [adding-markdown-pages](https://www.gatsbyjs.org/docs/adding-markdown-pages/) and [sourcing-from-netlify-cms](https://www.gatsbyjs.org/docs/sourcing-from-netlify-cms/) and my knowledge of how the page creation is done on the fly with `gatsby-node.js` and the API call `createPages` and how data is injected through the special prop `context` and then later retrieved in the template through `pageContext` prop. I find that the documentation is misleading and could lead to some misconceptions. From my read on this, in the `gatsby-node.js` you create the page with a supplied path, use a React component, either it is a functional one, or a full blown class one. And don't pass anything, or better, a empty object in the context. Right?
Now when you read the code from the documentation and your code, you have this:
```javascript
import React from "react"
import { graphql } from "gatsby"

export default function Template({
  data, // this prop will be injected by the GraphQL query below.
}) {
  const { markdownRemark } = data // data.markdownRemark holds our post data
  const { frontmatter, html } = markdownRemark
  return (
    <div className="blog-post-container">
      <div className="blog-post">
        <h1>{frontmatter.title}</h1>
        <h2>{frontmatter.date}</h2>
        <div
          className="blog-post-content"
          dangerouslySetInnerHTML={{ __html: html }}
        />
      </div>
    </div>
  )
}

export const pageQuery = graphql`
  query($path: String!) {
    markdownRemark(frontmatter: { path: { eq: $path } }) {
      html
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        path
        title
      }
    }
  }
`

You issue gatsby develop or gatsby build && gatsby serve and you see the results, but wait, where's the path coming from if an empty object was passed? It directly contradicts the other documentation available. Unless you have a keen eye and or some more extensive knowledge of how Gatsby works, this code will not make sense. Now here's the kicker, my read on this and i might be completely wrong and feel free to correct me on this, but it seems that the graphl query above will "drink" from the uri:/ prop, which in turn will be *:/test-post. I discovered this while looking at the development tools as you can see below. billie3

Probably a change to the documentation would be necessary in this case, if you're willing go for it.

shannonbux commented 5 years ago

Wondering if this issue might be somewhat relevant: #13708

billyjacoby commented 5 years ago

@shannonbux Yeah when I get the chance I plan on contributing to that issue.

@jonniebigodes I think your correct in assuming that this is not directly related to this issue, so I apologize for that.

I do have a few questions and possibly concerns regarding the tutorial for setting this up, versus the changes that you have made to my test repo. Please direct me to the correct place to ask this questions if this is not the right forum to do so.

The Netlify CMS Tutorial runs you through setting up Netlify CMS to work with GatsbyJS.

At the bottom of that tutorial is a link to the Adding Markdown Tutorial. For a third time I just ran through both of these tutorials and I think i figured out where the problem is; but i can't figure out why.

When running through the tutorial it doesn't matter if the "path" in front matter begins with a "/" or not. That is UNLESS I change the 'folder' location in the /static/admin/config.yml file. After this is changed, if there is no '/' in front of the frontmatter path, I get the mentioned error.