I'm setting up a simple blog page on an existing gatsby site, I want the person using netlify cms to be able to upload a thumbnail image for the blog post. I've managed to do so and locate it using graphigl
Now I would like to set it to my blog posts page:
import React from "react"
import { Link, graphql, useStaticQuery } from "gatsby"
import Layout from "../components/layout/layout"
const Blog = () => {
const data = useStaticQuery(graphql`
query {
allMarkdownRemark {
edges {
node {
frontmatter {
title
date
thumbnail {
childImageSharp {
fluid(maxWidth: 400) {
src
}
}
}
}
fields {
slug
}
}
}
}
}
`)
return (
<>
<Layout>
<main className="main">
<div className="articles">
<h1 className="articles__title">Articles</h1>
{data.allMarkdownRemark.edges.map(edge => {
return (
<section className="articles__list">
<a className="articles__article">
<div className="articles__article-artwork">
<figure className="articles__article-artwork-wrapper">
{edge.node.frontmatter.thumbnail.childSharpImage.fluid.src}
</figure>
</div>
<h2 className="articles__article-title">
<Link to={`/blog/${edge.node.fields.slug}`}>
{edge.node.frontmatter.title}
</Link>
</h2>
<Link>
<p>{edge.node.frontmatter.date}</p>
</Link>
<div className="articles__article-description">
<p></p>
</div>
<span className="articles__article-more">Read more...</span>
</a>
</section>
)
})}
</div>
</main>
</Layout>
</>
)
}
export default Blog
Then I get these errors, when redeployed on netlify.
config.yml
backend:
name: github
branch: development
repo: (removed for work reasons)
media_folder: static/img
public_folder: img
collections:
- name: "blog"
label: "Blog"
folder: "src/posts"
create: true
slug: "{{slug}}"
fields:
- {label: "Layout", name: "layout", widget: "hidden", default: "blog"}
- {label: "Title", name: "title", widget: "string"}
- {label: "Publish Date", name: "date", widget: "datetime"}
- {label: "Body", name: "body", widget: "markdown"}
- {label: "Image", name: "thumbnail", widget: "image"}
gatsby-node.js
const path = require('path')
module.exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions
if (node.internal.type === "MarkdownRemark") {
const slug = path.basename(node.fileAbsolutePath, '.md')
createNodeField({
node,
name: 'slug',
value: slug
})
}
}
module.exports.createPages = async ({ graphql, actions}) => {
const { createPage } = actions
const blogTemplate = path.resolve('./src/templates/blog.js')
const res = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
res.data.allMarkdownRemark.edges.forEach((edge) => {
createPage({
component: blogTemplate,
path: `/blog/${edge.node.fields.slug}`,
context: {
slug: edge.node.fields.slug
}
})
})
}
gatsby-config.js
module.exports = {
siteMetadata: {
title: `removed for work reasons`,
description: `removed`,
author: `removed`,
},
plugins: [
`gatsby-plugin-react-helmet`,
`gatsby-plugin-sass`,
`gatsby-plugin-remove-serviceworker`,
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `img`,
path: `${__dirname}/static/img`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `src`,
path: `${__dirname}/src`,
},
},
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
},
},
{
resolve: 'gatsby-plugin-react-svg',
options: {
rule: {
include: /assets/
}
}
},
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 590,
}
},
{
resolve: `gatsby-plugin-netlify-cms-paths`,
options: {
cmsConfig: `/static/admin/config.yml`
}
}
]
}
},
// this (optional) plugin enables Progressive Web App + Offline functionality
// To learn more, visit: https://gatsby.dev/offline
// `gatsby-plugin-offline`,
`gatsby-plugin-netlify-cms`,
`gatsby-plugin-netlify`,
],
}
I honestly thought that adding a blog page to my existing gatsby site using netlify cms would be a breeze but it has been one of the most difficult things I've attempted.
Any help is very much appreciated.
Thanks
There are a few things odd to me.
Your query is not working (so, breaking the code) because it can't find your image. Change your config.yml
media paths to:
media_folder: static/img
public_folder: /img
Note the slash (/
) in public_folder
path.
This is because it's a relative path and must start with a slash. From Netlify docs (bolded the slash part):
Public Folder
This setting is required.
The
public_folder
option specifies the folder path where the files uploaded by the media library will be accessed, relative to the base of the built site. For fields controlled by [file] or [image] widgets, the value of the field is generated by prepending this path to the filename of the selected file. Defaults to the value of media_folder, with an opening/
if one is not already included.public_folder: "/images/uploads"
Based on the settings above, if a user used an image widget field called
avatar
to upload and select an image calledphilosoraptor.png
, the image would be saved to the repository at/static/img/philosoraptor.png
, and the avatar field for the file would be set to/img/philosoraptor.png
.
Your media_folder
looks good.
The way you are rendering the image inside the <figure>
tag. Following your approach, it will render a string with the path of the image, however, you are using the sharp from gatsby-image
but you are not using it. I would recommend, among a few trials, the following:
<figure>
<Img fluid={edges.node.frontmatter.thumbnail.childImageSharp.fluid}>
</figure>
Following the gatsby-image
approach, you should also use a query fragment like:
const data = useStaticQuery(graphql`
query {
allMarkdownRemark {
edges {
node {
frontmatter {
title
date
thumbnail {
childImageSharp {
fluid(maxWidth: 400) {
...GatsbyImageSharpFluid
}
}
}
}
fields {
slug
}
}
}
}
}
`)
Note the ...GatsbyImageSharpFluid
fragment to fetch all needed data to use gatsby-image
.
You are using a staticQuery
but you don't need it, since all your data comes from a CMS. You should use a page/template query to improve the performance but this will change your page structure.
The "standard" way of creating dynamic pages in Gatsby is, using gatsby-node.js
to use createPage
API, pass the needed data to the template (normally an id
or slug
) and use that unique data to retrieve the blog/post information.
You are passing the slug
via context but you are never using it:
context: {
slug: edge.node.fields.slug
}
In addition, you are loopìng through all articles with the static query again (allMarkdownRemark
) what doesn't make sense and it's a waste of time and performance.
Your Blog
template should look like:
import React from 'react'
import { graphql } from 'gatsby'
const Blog = ({data}) => {
return (
<div>
Blog title is: {data.markdownRemark.frontmatter.title}
</div>
)
}
export const query = graphql`
query BlogQuery($slug: String!) {
query {
markdownRemark(fields: { slug: { eq: $slug }}) {
html
frontmatter {
title
date
thumbnail {
childImageSharp {
fluid(maxWidth: 400) {
...GatsbyImageSharpFluid
}
}
}
}
fields {
slug
}
}
}
}
`
export default Blog
Note that you are passing the slug ($slug
) as a required parameter (String!
), so it can't be null to a page query. Afterward, you are filtering the markdown nodes (markdownRemark
) to get the one that matches the context that you are passing in the gatsby-node.js
file. In other words, in that context, you have the data for each post.
Also notice that you may need to change the query in order to match your data structure, I've posted it from scratch without knowing your fields. Use the localhost:8000/___graphql
(GraphQL playground) to check it. Your fragments won't work in there since it's a limitation of the GraphQL but it will work on your code, so avoid its usage there but keep it in your code.