Gatsby JS Meta description is missing for SEO? - reactjs

I created an SEO component, but for some reason when I run my site through lighthouse or an SEO checker it says I'm missing a meta description.
Here is my SEO component
import React from "react"
import PropTypes from "prop-types"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"
function SEO({ description, title, keywords, siteUrl, lang, meta }) {
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
description
keywords
title
siteUrl
}
}
}
`
)
const metaDescription = description || site.siteMetadata.description
const defaultTitle = site.siteMetadata.title
const metaUrl = siteUrl || site.siteMetadata.siteUrl
const metaKeywords = keywords || site.siteMetadata.keywords
return (
<Helmet
htmlAttributes={{
lang,
}}
title={title}
titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : null}
meta={[
{
property: `og:title`,
content: title,
},
{
property: `og:siteurl`,
content: metaUrl,
},
{
name: `keywords`,
content: metaKeywords,
},
{
property: `og:description`,
content: metaDescription,
},
{
property: `og:type`,
content: `website`,
},
].concat(meta)}
/>
)
}
SEO.defaultProps = {
lang: `en`,
meta: [],
description: ``,
}
SEO.propTypes = {
description: PropTypes.string,
lang: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string.isRequired,
}
export default SEO
When I inspect my site, it shows
<meta data-react-helmet="true" property="og:description" content="My coding blog about tech
and design.">
So I'm not sure what I need to add or change for my SEO component because my current method of showing the description isn't working apparently based off lighthouse or the other SEO website checkers.

Just change the og:description to description (standalone):
{
property: `description`,
content: metaDescription,
},
og attributes stands for Open Graph. They are mostly used for social networks to get information when a web is shared, you can customize the description for those social networks instead of taking the description of the page itself. If you don't care about that, change it to description, if you do, keep both meta tags:
{
property: `og:description`,
content: anoterOgMetaDescription,
},
{
property: `description`,
content: metaDescription,
},

Related

SPFx web part similar to the Quick Links web part which check if the user have permission on the related links (internal and external links)

I am working on a project to build a portal inside SharePoint online. Now i got this requirement:-
To build a quick links web part in React SPFx (similar to the built-in modern Quick Links)
But this quick links can contain links to internal applications that can only be accessed using VPN.
So if the user access the SharePoint online home page without VPN >> the quick links should hide all the links which the user does not have access to.
Also if the user access the SharePoint using VPN and the user does not have permission to the internal system (the user will get http 401 or will be prompted with a dialog to enter username and password) to hide the links as well.
So in other words, the SPFx web part need to send a request to the link url, and if it get http code other than "2xx success" to hide the link.
Can anyone advice how to build such a web part please?
Now i tried to benefit from this SPFx web part # https://github.com/clarktozer/spfx-quicklinks . where this is the QuickLinksWebPart.ts:-
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '#microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneDropdown,
PropertyPaneCheckbox} from '#microsoft/sp-webpart-base';
import * as strings from 'QuickLinksWebPartStrings';
import QuickLinks from './components/QuickLinks';
import { IQuickLinksProps } from './components/IQuickLinksProps';
import { PropertyPaneLinksList } from '../../controls/PropertyPaneLinksList/PropertyPaneLinksList';
import { PropertyFieldColorPicker, PropertyFieldColorPickerStyle } from '#pnp/spfx-property-controls/lib/PropertyFieldColorPicker';
import { Link } from '../../controls/PropertyPaneLinksList/components/ILinksListState';
export interface IQuickLinksWebPartProps {
title: string;
type: LinkType;
iconColor: string;
openInNewTab?: boolean;
fontColor: string;
initLinks: string[];
links: Link[];
}
export enum LinkType {
LINK = "Link",
FILE = "File"
}
export default class QuickLinksWebPart extends BaseClientSideWebPart<IQuickLinksWebPartProps> {
public render(): void {
const element: React.ReactElement<IQuickLinksProps> = React.createElement(
QuickLinks,
{
title: this.properties.title,
type: this.properties.type,
iconColor: this.properties.iconColor,
fontColor: this.properties.fontColor,
openInNewTab: this.properties.openInNewTab,
links: this.properties.links != null ? this.properties.links : [],
displayMode: this.displayMode,
updateProperty: (value: string) => {
this.properties.title = value;
}
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneDropdown('type', {
label: strings.LinkType,
options: Object.keys(LinkType).map((e) => {
return {
key: LinkType[e], text: LinkType[e]
};
}),
selectedKey: 'link'
}),
PropertyPaneCheckbox('openInNewTab', {
text: strings.OpenInNewTab
})
]
},
{
groupName: strings.StylingGroup,
groupFields: [
PropertyFieldColorPicker('iconColor', {
label: strings.IconColor,
selectedColor: this.properties.iconColor,
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
properties: this.properties,
disabled: false,
alphaSliderHidden: false,
style: PropertyFieldColorPickerStyle.Inline,
key: 'iconColor'
}),
PropertyFieldColorPicker('fontColor', {
label: strings.FontColor,
selectedColor: this.properties.fontColor,
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
properties: this.properties,
disabled: false,
alphaSliderHidden: false,
style: PropertyFieldColorPickerStyle.Inline,
key: 'fontColor'
})
]
},
{
groupName: strings.LinksGroup,
groupFields: [
new PropertyPaneLinksList("links", {
key: "links",
links: this.properties.links
})
]
}
]
}
]
};
}
}
and this is the QuikcLinks.tsx :-
import * as React from 'react';
import styles from './QuickLinks.module.scss';
import { IQuickLinksProps } from './IQuickLinksProps';
import { autobind } from '#uifabric/utilities';
import { LinkType } from '../QuickLinksWebPart';
import { WebPartTitle } from "#pnp/spfx-controls-react/lib/WebPartTitle";
import Radium from 'radium';
import * as tinycolor from 'tinycolor2';
#Radium
export default class QuickLinks extends React.Component<IQuickLinksProps, {}> {
private inlineStyles: any;
constructor(props: IQuickLinksProps) {
super(props);
}
#autobind
private createLinkStyle(hoverColor) {
return {
color: this.props.fontColor,
':hover': {
color: tinycolor(hoverColor).darken(25).toString()
}
};
}
#autobind
public getIcon() {
let icon = "";
switch (this.props.type) {
case LinkType.FILE:
icon = "OpenFile";
break;
default:
icon = "Link";
}
return icon;
}
public render(): React.ReactElement<IQuickLinksProps> {
this.inlineStyles = {
link: this.createLinkStyle(this.props.fontColor)
};
return (
<div className={"ms-Grid " + styles.quickLinks}>
<div>
<div className="ms-Grid-row">
<div className="ms-Grid-col ms-sm12">
<WebPartTitle displayMode={this.props.displayMode}
title={this.props.title}
updateProperty={this.props.updateProperty} />
{
this.props.links.map((e, i) => {
let linkProps = {
key: e.key,
href: e.value
};
if (this.props.openInNewTab) {
linkProps["target"] = "_blank";
}
return <div className={styles.linkRow} key={this.props.type + "-link-" + i}>
<i style={{ color: this.props.iconColor }} className={styles.quickLinkIcon + " ms-Icon ms-Icon--" + this.getIcon()} aria-hidden="true"></i>
<a className={styles.link} {...linkProps} style={this.inlineStyles.link}>{e.label}</a>
</div>;
})
}
</div>
</div>
</div>
</div>
);
}
}
So where i need to do the call to the URL and check the response so i can render the link or hide it accordingly??
Thanks
To hide links that the user does not have access to, you can add a check in the web part's render method to determine whether to show or hide each link. You can use the fetch API to send a request to each link's URL, and check the response code to determine whether to show or hide the link. If the response code is anything other than a 2xx success code, you can hide the link.
Here is an example of how you might implement this:
import * as React from 'react';
import * as ReactDom from 'react-dom';
// Other imports here...
export default class QuickLinksWebPart extends BaseClientSideWebPart<IQuickLinksWebPartProps> {
public render(): void {
const links = this.properties.links.map(async (link: Link) => {
// Send a request to the link's URL
const response = await fetch(link.url);
// Check the response code
if (response.ok) {
// If the response code is a 2xx success code, return the link
return link;
} else {
// If the response code is not a 2xx success code, return null
return null;
}
});
const element: React.ReactElement<IQuickLinksProps> = React.createElement(
QuickLinks,
{
title: this.properties.title,
type: this.properties.type,
iconColor: this.properties.iconColor,
fontColor: this.properties.fontColor,
openInNewTab: this.properties.openInNewTab,
links: links, // Set the links to the updated list of links
displayMode: this.displayMode,
updateProperty: (value: string) => {
this.properties.title = value;
}
}
);
ReactDom.render(element, this.domElement);
}
// Other methods here...
}
In the above example, the render method first sends a request to each link's URL and checks the response code. If the response code is a 2xx success code, the link is added to the list of links to be displayed. If the response code is not a 2xx success code, the link is not added to the list. The updated list of links is then passed to the QuickLinks component.

Next js seo not working when sharing link

Im currently trying to implement SEO for a next.js and react webside, im using next-seo library for this. But i have the problem that when i share the link of the different sections i alway get the default title and description.
if i inspect the page, the title, description and openGraph options are been change, but at the moment of sharing the link of the page / pages or sharing on social media using the library next-share the default config is always taken. Im using getStaticProps() for most of the pages and getServerSideProps() for the my post section.
_app.tsx
const clientSideEmotionCache = createEmotionCache();
interface CustomAppProps extends AppProps {
emotionCache?: EmotionCache;
}
function ApinityApp({ Component, emotionCache = clientSideEmotionCache, pageProps }: CustomAppProps) {
return (
<>
<DefaultSeo {...SEO} />
<ApinityHomeUtilTracking containerId={process.env.NEXT_PUBLIC_PIWIK_ID}>
<HideAndSeek>
<MainMenuProvider>
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<SnackbarProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</SnackbarProvider>
</ThemeProvider>
</CacheProvider>
</MainMenuProvider>
</HideAndSeek>
</ApinityHomeUtilTracking>
</>
);
}
export default ApinityApp;
Default config
const SEO = {
title: 'API Business Operations SaaS platform | apinity.io',
description:
'Our SaaS platform for API Business Operations enables you to get the most business value from your APIs and make them part of your future API strategy.',
noindex: false,
openGraph: {
type: 'website',
locale: 'en_DE',
url: 'https://apinity.io/',
title: 'API Business Operations SaaS platform | apinity.io',
description:
'Our SaaS platform for API Business Operations enables you to get the most business value from your APIs and make them part of your future API strategy.',
images: [
{
url: 'https://apinity.io/images/logos/Apinity-logo-white-text.svg',
width: 139,
height: 35,
alt: 'apinity GmbH logo',
},
],
site_name: 'apinity GmbH',
},
twitter: {
handle: '#handle',
site: '#site',
cardType: 'summary_large_image',
},
};
export default SEO;
About page
export function AboutUs({ cmsData, menu }: { cmsData: StrapiMarketingAboutUs; menu: MainMenuItem[] }) {
const { aboutUsHome, ourTeamDetail, apinityLeaders, ourApinityStory, apinityOurAwards, apinityInvestor, apinityPartners, apinityScaleUp, getInTouch, seo } =
cmsData.marketingAboutUs.data.attributes;
const { setMenuItems } = useMainMenu();
setMenuItems(menu);
return (
<>
<NextSeo {...seoConfigAdapter(seo)} />
seoConfigAdapter
export const seoConfigAdapter = (seo) => {
if (!seo) {
return {};
}
const { metaTitle, metaDescription, metaRobots, canonicalURL, keywords, metaImage } = seo;
return {
title: metaTitle,
description: metaDescription,
canonical: canonicalURL,
noindex: false,
openGraph: {
type: 'website',
locale: 'en_IE',
url: window.location.href,
title: metaTitle,
description: metaDescription,
images: metaImage
? [
{
url: metaImage.data.attributes.url,
width: metaImage.data.attributes.width,
height: metaImage.data.attributes.height,
alt: metaImage.data.attributes.name,
},
]
: [],
},
additionalMetaTags: [
{
name: 'keywords',
content: keywords,
},
{
name: 'googlebot',
content: metaRobots,
},
],
};
};

(Gatsby) How to pass image source as a prop in MDX component

I'm trying to create a Figure component in which I pass the img src along with other data.
I realize it's not straightforward—e.g., this thread—but I thought it would work fine with normal HTML img tags.
The image is not displaying. Does that mean that this limitation also applies to HTML img tags within components?
If this is the case, I guess I indeed ought to use Gatsby's dynamic images. How would I do this in a static query (nonpage component)? This thread had me believing it isn't possible—or at least a hack?
The component inside MDX documents:
<Figure
image=""
size=""
caption=""
credits=""
/>
Figure.js:
import * as React from "react"
const Figure = ({ image, size, caption, credits }) => {
return (
<figure className={size}>
<img src={image} alt={caption} />
<figcaption>
<span>{caption}</span>
<span>{credits}</span>
</figcaption>
</figure>
)
}
export default Figure
articlePostTemplate.js
import * as React from "react"
import { graphql } from "gatsby"
import { MDXRenderer } from "gatsby-plugin-mdx"
import Layout from "../components/layout.js"
import Seo from "../components/seo.js"
const PostTemplate = ({ data, location }) => {
let post = data.mdx
return (
<Layout location={location}>
<Seo
title={post.frontmatter.title}
description={post.frontmatter.lead}
date={post.frontmatter.computerDate}
/>
<article className="post">
<header className="post" id="intro">
<p className="date">
<time dateTime={post.frontmatter.computerDate}>{post.frontmatter.humanDate}</time>
</p>
<h1 itemprop="headline">{post.frontmatter.title}</h1>
<p className="lead">{post.frontmatter.lead}</p>
</header>
<section className="post" id="body-text">
<MDXRenderer data={data}>{post.body}</MDXRenderer>
</section>
</article>
</Layout>
)
}
export default PostTemplate
export const pageQuery = graphql`
query PostBySlug(
$id: String!
) {
site {
siteMetadata {
title
}
}
mdx(id: { eq: $id }) {
id
excerpt(pruneLength: 160)
body
frontmatter {
title
computerDate: date(formatString: "YYYY-MM-DD")
humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
hook
type
lead
featuredImage {
childImageSharp {
fluid(maxWidth: 800) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`
gatsby-config.js
module.exports = {
…
},
plugins: [
`gatsby-plugin-image`,
`gatsby-plugin-sitemap`,
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.md`, `.mdx`],
gatsbyRemarkPlugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: `900000000000`,
linkImagesToOriginal: false,
backgroundColor: `none`,
},
},
{
resolve: `gatsby-remark-responsive-iframe`,
options: {
wrapperStyle: `margin-bottom: 1.07var(--line-length)`,
},
},
`gatsby-remark-prismjs`,
`gatsby-remark-copy-linked-files`,
`gatsby-remark-smartypants`,
{
resolve: `gatsby-remark-autolink-headers`,
options: {
icon: false,
itemprop: `heading`,
maintainCase: false,
removeAccents: true,
elements: [`h2`, `h3`, `h4`],
},
}
],
},
},
…
{
resolve: `gatsby-source-filesystem`,
options: {
name: `content`,
path: `${__dirname}/content/`,
}
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/data`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/images`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/pages/`,
}
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `journalistikk`,
path: `${__dirname}/content/journalism/`,
}
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `discussion`,
path: `${__dirname}/content/discussion/`,
}
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `photography`,
path: `${__dirname}/content/photography/`,
}
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `Gatsby Starter Blog`,
short_name: `GatsbyJS`,
start_url: `/`,
background_color: `#ffffff`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`,
},
},
`gatsby-plugin-react-helmet`,
],
}
gatsby-node.js
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
const articlePostTemplate = path.resolve(`./src/templates/articlePostTemplate.js`)
const result = await graphql(
`
{
allMdx(
sort: { fields: [frontmatter___date], order: ASC }
limit: 1000
) {
nodes {
id
frontmatter {
title
computerDate: date(formatString: "YYYY-MM-DD")
humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
}
fields {
slug
}
}
}
}
`
)
if (result.errors) {
reporter.panicOnBuild(
`There was an error loading your blog posts`,
result.errors
)
return
}
const posts = result.data.allMdx.nodes
if (posts.length > 0) {
posts.forEach((post, index) => {
[index + 1].id
createPage({
path: post.fields.slug,
component: articlePostTemplate,
context: {
id: post.id
},
})
})
}
}
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `Mdx`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
createTypes(`
type SiteSiteMetadata {
author: Author
siteUrl: String
social: Social
}
type Author {
name: String
summary: String
}
type Social {
twitter: String
instagram: String
mail: String
}
type MarkdownRemark implements Node {
frontmatter: Frontmatter
fields: Fields
}
type Frontmatter {
title: String
description: String
date: Date #dateformat
}
type Fields {
slug: String
}
`)
}
layout.js
import * as React from "react"
import { MDXProvider } from "#mdx-js/react"
import { Link } from "gatsby"
import DWChart from "react-datawrapper-chart"
import Header from "./Header"
import Footer from "./Footer"
import Figure from "./Figure"
const shortcodes = { Link, DWChart, Figure }
export default function Layout({ children }) {
return (
<div className="layout-wrapper">
<Header />
<main>
<MDXProvider components={shortcodes}>{children}</MDXProvider>
</main>
<Footer />
</div>
)
}
The limitation you mention in How to pass a path of image as a prop in Gatsby in Gatsby-plugin-image only applies to StaticImage + dynamic props data (with a dynamic source), meaning that the component that returns a StaticImage cannot receive the src as a props, because all the props of StaticImage needs to be statically analyzed.
In other words, your MDX can receive a src to be used in a <img> tag or you can use GatsbyImage component if properly configured.
Keep in mind that the query that will fetch GatsbyImage data (childImageSharp, gatsbyImageData, etc) must be placed in a top-level component (pages) if using a page query or in a useStaticQuery hook if used elsewhere.
The approach in both scenarios (using <img> or GatsbyImage) is similar. You need to:
Provide your image sources in your markdown file
Query those images using a page query or useStaticQuery to provide to your MDXProvider the queried image props. Using the blog example:
<MDXProvider>
<MDXRenderer data={data}/>
</MDXProvider>
data stands for your queried data in a GraphQL page query or useStaticQuery. Without knowing your data structure I haven't add the GraphQL part because I do not know the nodes available.
At this point, your MDXProvider holds the images data (all from your markdown files), you just need to provide it to your GatsbyImage or Figure component:
// some.mdx
import Figure from 'path/to/figure/component';
## Post Body
Lorem ipsum dolor...
<Figure img={props.data} />
As you can see, I lift all props to Figure just to allow to debug (using console.logs() for example) to see the available props there like:
const Figure = (props) => {
console.log(props);
return (
<div>I will be a figure in a future</div>
)
}
export default Figure
In that way, you will be able to pass to Figure something like img={props.someNode.frontmatter.imageNodeSource}.
Again, without knowing your data structure is like a pie in the sky but get the idea.
Hat tip to Ferran for helpful guidance.
After more research, I revised my solution—
articleTemplate.js
/* shortcodes */
const ArticleTemplate = ({ data, location }) => {
let post = data.mdx
return (
<Layout location={location}>
<Seo
title={post.frontmatter.title}
description={post.frontmatter.lead}
date={post.frontmatter.computerDate}
/>
<article className="article">
<p className="date">
<time dateTime={post.frontmatter.computerDate}>{post.frontmatter.humanDate}</time>
</p>
<h1 itemprop="headline">{post.frontmatter.title}</h1>
<p className="lead" itemprop="introduction">{post.frontmatter.lead}</p>
<MDXProvider components={shortcodes}>
<MDXRenderer
data={post.frontmatter.thumbnail}
localImages={post.frontmatter.embeddedImagesLocal}
>
{post.body}
</MDXRenderer>
</MDXProvider>
</article>
</Layout>
)
}
export default ArticleTemplate
export const pageQuery = graphql`
query ArticleBySlug($id: String!) {
site {
siteMetadata {
title
}
}
mdx(id: {eq: $id}) {
id
excerpt(pruneLength: 160)
body
frontmatter {
title
computerDate: date(formatString: "YYYY-MM-DD")
humanDate: date(formatString: "D. MMMM YYYY", locale: "nb")
hook
type
lead
thumbnail {
childImageSharp {
gatsbyImageData(
layout: FULL_WIDTH
)
}
}
embeddedImagesLocal {
childImageSharp {
gatsbyImageData
}
}
}
}
}
`
figure.js
import * as React from "react"
import { GatsbyImage, getImage } from 'gatsby-plugin-image'
const Figure = ({ source, size, caption, credit }) => {
return (
<figure className={size}>
<GatsbyImage image={getImage(source)} alt={caption} />
<figcaption>
<span>{caption}</span>
<span>{credit}</span>
</figcaption>
</figure>
);
}
export default Figure
index.mdx
---
…
thumbnail: "thumb.jpeg"
embeddedImagesLocal:
- "first.jpeg"
- "second.jpeg"
---
<Figure
source={(props.localImages [0])} <!-- first image; second image would be `[1]` -->
size="I'm a `className`"
caption="I'm a caption"
photographer="I'm a name."
/>
(Marking this as the solution as it's the most helpful for anyone looking to do this in the future. It also shows how to query for embedded images and featured images—at the same time.)

How do I fix "TypeError: childImageSharp is undefined" on NetlifyCMS build?

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 called philosoraptor.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.

How to correct render data from array?

App.js
import { useSiteMetadata } from "../hooks/use-site-metadata"
export default function () {
const { projectItems } = useSiteMetadata()
return (
<Box flex flex-wrap items-start mb5>
{projectItems.map((value, index, category) => {
return (
<ProjectItem
key={index}
name={value.name}
title={value.title}
image={value.image}
className="border-box"
exerpt={value.exerpt}
git={value.git}
url={value.url}
category={value.category}
click="Push"
sans-serif
mb0-l
mb3
flex-none
w5
mr3
/>
)
})}
</Box>
)
}
site Metadata
projectItems: [
{
name: `1project`,
title: `1 title`,
image: `test-pic.jpg`,
exerpt: `some first project`,
git: `https://github.com/desmukh/gatsby-starter-woo/tree/master/`,
url: `https://www.gatsbyjs.com/plugins/gatsby-plugin-smoothscroll/`,
category: ["all", "mobile", "ux-ui"],
},
{
name: `2project`,
title: `2 title`,
image: `photo-2.jpg`,
exerpt: `some 2 project`,
git: `https://github.com/desmukh/gatsby-starter-woo/tree/master/`,
url: `https://www.gatsbyjs.com/plugins/gatsby-plugin-smoothscroll/`,
category: ["all", "mobile", "ux-ui"],
},
{
name: `3project`,
title: `3 title`,
image: `photo-2.jpg`,
exerpt: `some 3 project`,
git: `https://github.com/desmukh/gatsby-starter-woo/tree/master/`,
url: `https://www.gatsbyjs.com/plugins/gatsby-plugin-smoothscroll/`,
category: ["all", "mobile", "ux-ui"],
},
],
site-metadata-query
import { useStaticQuery, graphql } from "gatsby"
export const useSiteMetadata = () => {
const { site } = useStaticQuery(
graphql`
query MyQuery {
site {
siteMetadata {
title
description
author {
name
}
sections
projectItems {
name
title
image
exerpt
git
url
category
}
favicon
logo
menuItems {
path
label
}
social {
service
url
fa
}
footer {
text
logo
}
address {
line1
line2
line3
}
contacts {
text
url
}
}
}
}
`
)
return site.siteMetadata
}
How to fetch data from the site Metadata from the the categories items array, so it will look like list of items, one by one. Now the problem is that data appear like one piece of data(like on the picture), but there are few items in the array. All code is here
Well, the problem was i didn't specify - how to access the elements in the arrays that are within the objects. With different words - in the return statement that map all array should be second map with the return statement and indentation is very important. If someone faced with same problem - this thread is very helpful same problem with react, but react with old version so be mind

Resources