There's not a page yet at /second%20post - reactjs

I have an issue regarding my gatsby site. I am fetching content from contentful and according to the code in my gatsby-node.js it has to generate two pages and it does but only one of them is working when I click on it for the second one it show that
There's not a page yet at /second%20post
I am so confused cause most of the issues asked here telling that they are not able to generate the pages and I don't know if the page is created or not and if it is created then why it shows me the error message and also when got to the error page the
second post
link is given but it is non clickable. all the other code is in my git repository here at Github code
Pleas refer to image for clear understanding at
Image here
here is my gatsby-node.js code file
const path = require(`path`)
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions;
// Define a template for blog post
const blogPost = path.resolve(`./src/templates/blog-post-contentful.js`)
// Get all markdown blog posts sorted by date
const result = await graphql(
`
{
allContentfulBlockchainlearning{
edges{
node{
slug
title
subtitle
}
}
}
}
`
)
if (result.errors) {
reporter.panicOnBuild(
`There was an error loading your blog posts`,
result.errors
)
return
}
const posts = result.data.allContentfulBlockchainlearning.edges
// Create blog posts pages
// But only if there's at least one markdown file found at "content/blog" (defined in gatsby-
config.js)
// `context` is available in the template as a prop and as a variable in GraphQL
if (posts.length > 0) {
posts.forEach((post, index) => {
const previousPostSlug = index === 0 ? null : posts[index - 1].id
const $nextPostSlug = index === posts.length - 1 ? null : posts[index + 1].id
createPage({
path: post.node.slug,
component: blogPost,
context: {
slug: post.node.slug,
previousPostSlug,
$nextPostSlug,
},
})
})
}
}
and here is my blog-post template I want to creat
import React from "react"
import { Link, graphql } from "gatsby"
import Bio from "../components/bio"
import Layout from "../components/layout"
import SEO from "../components/seo"
const BlogPostTemplate = ({ data, location }) => {
const post = data.contentfulBlockchainlearning
const siteTitle = data.site.siteMetadata?.title || `Title`
const { previous, next } = data
return (
<Layout location={location} title={siteTitle}>
<SEO
title={post.title}
description={post.subtitle}
/>
<article
className="blog-post"
itemScope
itemType="http://schema.org/Article"
>
<header>
<h1 itemProp="headline">{post.title}</h1>
<p>{post.date}</p>
</header>
<section
dangerouslySetInnerHTML={{ __html: post.content.raw }}
itemProp="articleBody"
/>
<hr />
<footer>
<Bio />
</footer>
</article>
<nav className="blog-post-nav">
<ul
style={{
display: `flex`,
flexWrap: `wrap`,
justifyContent: `space-between`,
listStyle: `none`,
padding: 0,
}}
>
<li>
{previous && (
<Link to={previous.slug} rel="prev">Hey There
← {previous.title}
</Link>
)}
</li>
<li>
{next && (
<Link to={next.slug} rel="next">
{next.title} →
</Link>
)}
</li>
</ul>
</nav>
</Layout>
)
}
export default BlogPostTemplate
export const pageQuery = graphql`
query BlogPostBySlug(
$slug: String!
$previousPostSlug: String
$nextPostSlug: String
) {
site {
siteMetadata {
title
}
}
contentfulBlockchainlearning(slug: {eq: $slug}){
title
subtitle
content{
raw
}
}
previous: contentfulBlockchainlearning(slug: { eq: $previousPostSlug}) {
title
}
next: contentfulBlockchainlearning(slug: { eq: $nextPostSlug }) {
title
}
}
`

The issue is simple, you can't create a URL with whitespace like the one you are trying to create. second page should be parsed as second-page since the whitespace between second and page potentially will cause a lot of issues.
Gatsby is creating properly the pages since they appear on the 404 page (under gatsby develop, the 404 page lists all you created pages). However, it doesn't have a valid route because your slugs must be slugified. Ideally, the slug should be fetched with the correct format from the CMS already, however, you can add some controls to avoid this behaviour:
if (posts) {
posts.forEach((post, index) => {
let slugifiedPath= post.node.slug.toLowerCase().replace(/\s/g, '-');
const previousPostSlug = index === 0 ? null : posts[index - 1].id
const $nextPostSlug = index === posts.length - 1 ? null : posts[index + 1].id
createPage({
path: slugifiedPath,
component: blogPost,
context: {
slug: post.node.slug,
previousPostSlug,
$nextPostSlug,
},
})
})
}
It's quite self-explanatory but, since your paths are being fetched with a wrong format, you need to refactor them by:
let slugifiedPath= post.node.slug.toLowerCase().replace(/\s/g, '-');
It transforms it to lower case and it replaces all-white spaces globally (/\s/g) using a regular expression for hyphens (-), creating a valid slug.

Related

Confusion about handling graphQL data in a Nextjs project

I'm building a webpage with Next.js and graphQL, using Apollo as a client.
On on the main page (index.js) I want a "capsules" and "past launches" link. When the user clicks on either of these links, it directs them to a page detailing either the capsules or the past launches, where mission names are displayed. This details page is controlled by [parameters].js
The problem is this: I want render either the name of the capsule or the launch, depending on what the user has clicked on. However, in the data returned from the API call, the name properties are described using different keys: for capsules the key is name and past launches is mission_name. API data as follows:
{
"data": {
"capsules": [
{
"landings": 1,
"missions": [
{
"flight": 14,
"name": "CRS-3"
}
]
}
],
"launchesPast": [
{
"mission_name": "Starlink-15 (v1.0)",
"id": "109"
}
]
}
}
This presents an issue, because when I'm mapping through the returned data in [parameters].js I can only use either name or mission_name to select and render the name property - which isn't useful since I want to dynamically render the name of whatever element the user clicks on.
What's the best way to handle my API data/structure my project so I can render the relevant data, despite different key names?
Here's my index page - please ignore the other links:
index.js
import { useContext } from 'react'
import { LaunchContext } from '../spacexContext'
import Link from 'next/link'
import Card from '../components/Card'
export const Home = () => {
const data= useContext(LaunchContext)
return (
<div>
<Link href = {`/launchesPast`}>
<div>{data.launchesPast.length} launches last year</div>
</Link>
<Link href = {`/launchesUpcoming`}>
<div> Upcoming launches</div>
</Link>
<Link href = {`/missions`}>
<div>Missions</div>
</Link>
<Link href = {`/payloads`}>
<div> Payloads</div>
</Link>
<Link href = {`/users`}>
<div>Users</div>
</Link>
<Link href = {`/coresPast`}>
<div>Used cores to date</div>
</Link>
</div>
)
}
export default Home
[parameters].js
import { useRouter } from 'next/router'
import { useContext,useState,useEffect } from 'react'
import { LaunchContext } from '../spacexContext'
const Items = () => {
const [path, setPath] = useState("");
const data = useContext(LaunchContext);
const router = useRouter();
useEffect(() => {
if (router.isReady) {
setPath(router.query.parameter);
}
}, [router.isReady]);
return (
<div>
{data &&
path &&
data[path].map((item, i) => {
return (
<>
<p>{item.mission_name}</p>
<p>{item.launch_date_local}</p>
</>
)
})}
</div>
);
};
export default Items;
It occured to me to have some sort of conditional in [paramter].js as follows:
let name
if(item.mission_name){
name = mission_name
} else if (item.name) {
name = item.name
}
...however, this feels like a problematic solutution.

Vercel: ERROR Error: The package "esbuild-linux-64" could not be found, and is needed by esbuild

Hope you all are having a good time. I am working on a simple NextJs application where I want to have multiple subdomains in it. I am deploying the application on vercel.
What my application does is it have a simple textarea where you write MDX, you click the Publish button and it will save that MDX into firebase firestore. Below the textarea it shows the list of all the pages that have been published before.
The application renders the list of all the pages like with name of the page which is randomly generated as the subdomain while the actual domain comes later like the following.
a-mdx-page.mydomain.app
When I open that URL it will fetch the page MDX from firestore and uses next-mdx-remote package to serialize and render the MDX. The reason for using the next-mdx-remote package is that we can add react components in MDX and it can render as normal react components. I already have a custom domain because you cannot have a subdomain on top of a free subdomain in vercel free deployment.
All works fine on localhost and everything is as it should be but the problem is when I deploy the code on Vercel and navigate to subdomain it shows ERROR 500 on the website and shows the following error in the logs.
[GET] / 21:21:03:30
2021-10-24T16:21:04.018Z 8e52d5da-ff1f-4840-a09b-199233834a5d ERROR Error: The package "esbuild-linux-64" could not be found, and is needed by esbuild.
If you are installing esbuild with npm, make sure that you don't specify the
"--no-optional" flag. The "optionalDependencies" package.json feature is used
by esbuild to install the correct binary executable for your current platform.
at generateBinPath (/var/task/node_modules/esbuild/lib/main.js:1643:15)
at esbuildCommandAndArgs (/var/task/node_modules/esbuild/lib/main.js:1699:11)
at ensureServiceIsRunning (/var/task/node_modules/esbuild/lib/main.js:1856:25)
at Object.transform (/var/task/node_modules/esbuild/lib/main.js:1751:37)
at serialize (/var/task/node_modules/next-mdx-remote/dist/serialize.js:287:43)
at async getServerSideProps (/var/task/.next/server/pages/index.js:261:25)
at async Object.renderToHTML (/var/task/node_modules/next/dist/server/render.js:428:24)
at async doRender (/var/task/node_modules/next/dist/server/next-server.js:1144:38)
at async /var/task/node_modules/next/dist/server/next-server.js:1236:28
at async /var/task/node_modules/next/dist/server/response-cache.js:64:36 {
page: '/'
}
RequestId: 8e52d5da-ff1f-4840-a09b-199233834a5d Error: Runtime exited with error: exit status 1
Runtime.ExitError
From what I understand that the next-mdx-remote serialize function uses esbuild in it and when I deploy the application on vercel npm just doesn't downloads the platform specific package of it but may be I am wrong.
I have tried to search the solution for this but there is not any answers that helped me.
Following is all the code that the application uses.
import { useState } from "react"
import { collection, doc, getDoc, getDocs, setDoc } from "firebase/firestore"
import matter from "gray-matter"
import { MDXRemote } from "next-mdx-remote"
import { serialize } from "next-mdx-remote/serialize"
import {
uniqueNamesGenerator,
adjectives,
colors,
animals,
} from "unique-names-generator"
import { db } from "../utils/fire-client"
import Layout from "../components/Layout"
import { HOSTNAME } from "../config"
import MDXComponents from "../components/mdx"
export default function Index({ posts, isPage = false, mdxSource }) {
const [mdxCode, setMdxCode] = useState("# THIS IS MDX")
const [message, setMessage] = useState("")
const addPageToCollection = async (name, content) => {
const pagesCollection = collection(db, "pages")
await setDoc(doc(pagesCollection, name), {
name,
content,
})
}
function publishPage() {
const randomName = uniqueNamesGenerator({
dictionaries: [adjectives, colors, animals],
})
addPageToCollection(randomName, mdxCode)
setMessage(
"New Page Added: " + randomName + "\nReload page To see it in the list"
)
setTimeout(() => {
setMessage("")
}, 5000)
}
return (
<Layout>
{isPage ? (
<>
<header>
<nav>
<a href={"http://" + HOSTNAME}>
<a>👈 Go back home</a>
</a>
</nav>
</header>
<main>
<MDXRemote {...mdxSource} components={MDXComponents} />
</main>
</>
) : (
<>
<h1>Home Page</h1>
<textarea
name="mdxCode"
id="mdxCode"
value={mdxCode}
onChange={(e) => setMdxCode(e.target.value)}
className="w-full h-1/2 border-2 border-gray-400 p-2"
/>
<button className="btn btn-primary" onClick={publishPage}>
Publish
</button>
<div>{message}</div>
<ul>
<div className="mt-4 font-bold">Pages List</div>
{posts.map((post) => (
<li key={post.name}>
<a href={`http://${post.name}.${HOSTNAME}`}>{post.name}</a>
</li>
))}
</ul>
</>
)}
</Layout>
)
}
export async function getServerSideProps({ req, res }) {
const host = req.headers.host.split(".")
if (host[0] !== HOSTNAME.split(".")[0] && host[0] !== "www") {
const docRef = doc(db, "pages", host[0])
const docSnap = await getDoc(docRef)
if (docSnap.exists()) {
const { content, data } = matter(docSnap.data().content)
const mdxSource = await serialize(content, {
// Optionally pass remark/rehype plugins
mdxOptions: {
remarkPlugins: [],
rehypePlugins: [],
},
scope: data,
})
if (mdxSource) {
return {
props: {
isPage: true,
mdxSource,
},
}
}
} else {
return {
props: {
redirect: {
destination: "/",
},
},
}
}
}
const pagesCollection = collection(db, "pages")
const pagesSnapshot = await getDocs(pagesCollection)
const pagesList = pagesSnapshot.docs.map((doc) => doc.data())
if (pagesList.length > 0) {
return {
props: {
posts: pagesList,
},
}
}
return { props: { posts } }
}
Update esbuild to 0.13.4 or higher
npm i -D esbuild#0.13.4
See: https://github.com/evanw/esbuild/releases/tag/v0.13.4
If building with Docker this could be due to an incomplete .dockerignore file that doesn't ignore all your node_modules folder.

Gatsby fetching Wordpress Custom Post Types and create pages

I read the documentation and tried several tutorials, but I am stuck on fetching custom post types with GatsbyJS.
I tried several approaches, but none of them are working as expected. I always receive a 404.
This is a part of the snippet I am using, which works fine with pages and posts, but not with a custom post type.
The projects pages should be created under a project subfolder/path. Like: example.com/project/my-first-project
The part of the gatsby-node.js looks like that:
const createSinglePages = async ({ posts, gatsbyUtilities }) =>
Promise.all(
posts.map(({ previous, post, next }) =>
// createPage is an action passed to createPages
// See https://www.gatsbyjs.com/docs/actions#createPage for more info
gatsbyUtilities.actions.createPage({
// Use the WordPress uri as the Gatsby page path
// This is a good idea so that internal links and menus work 👍
path: post.uri,
// use the blog post template as the page component
component: path.resolve(
`./src/templates/${post.__typename.replace(`Wp`, ``)}.js`
),
// `context` is available in the template as a prop and
// as a variable in GraphQL.
context: {
// we need to add the post id here
// so our blog post template knows which blog post
// the current page is (when you open it in a browser)
id: post.id,
// We also use the next and previous id's to query them and add links!
previousPostId: previous ? previous.id : null,
nextPostId: next ? next.id : null,
},
})
)
);
The src/template/project.js file looks like this:
import React from "react";
import { Link, graphql } from "gatsby";
import Image from "gatsby-image";
import parse from "html-react-parser";
import Layout from "../components/Layout";
import Seo from "../components/Seo";
const ProjectTemplate = ({ data: { post } }) => {
const featuredImage = {
fluid: post.featuredImage?.node?.localFile?.childImageSharp?.fluid,
alt: post.featuredImage?.node?.alt || ``,
};
return (
<Layout>
<Seo title={post.title} description={post.excerpt} />
<article
className="blog-post"
itemScope
itemType="http://schema.org/Article"
>
<header>
<h1 itemProp="headline">{parse(post.title)}</h1>
<p>{post.date}</p>
{/* if we have a featured image for this post let's display it */}
{featuredImage?.fluid && (
<Image
fluid={featuredImage.fluid}
alt={featuredImage.alt}
style={{ marginBottom: 50 }}
/>
)}
</header>
{!!post.content && (
<section itemProp="articleBody">{parse(post.content)}</section>
)}
</article>
</Layout>
);
};
export default ProjectTemplate;
export const pageQuery = graphql`
query ProjectById(
# these variables are passed in via createPage.pageContext in gatsby-node.js
$id: String!
) {
# selecting the current post by id
post: wpProject(id: { eq: $id }) {
id
content
title
date(formatString: "MMMM DD, YYYY")
featuredImage {
node {
altText
localFile {
childImageSharp {
fluid(maxWidth: 1000, quality: 100) {
...GatsbyImageSharpFluid_tracedSVG
}
}
}
}
}
}
}
`;
Is the Gatsby API creating a subfolder automatically, or do I need to define that somewhere for each post type?
Any help appreciated!
You define the "subfolder" under the path field in:
gatsbyUtilities.actions.createPage({
path: post.uri,
component: path.resolve(
`./src/templates/${post.__typename.replace(`Wp`, ``)}.js`
),
context: {
id: post.id,
previousPostId: previous ? previous.id : null,
nextPostId: next ? next.id : null,
},
})
You just need to do something like:
path: `projects/${post.id}`
Check the slashes and trailing slashes here.
You cna replace projects for your dynamic project type if you fetch that information for a more automatic approach (assuming it's post.__typename).
In order to use Custom Post Types with WPGraphQL, you must configure the Post Type to show_in_graphql using the following field:
show_in_graphql : true
While Registering a new Custom Post Type
This is an example of registering a new "docs" post_type and enabling GraphQL Support.
add_action( 'init', function() {
register_post_type( 'docs', [
'show_ui' => true,
'labels' => [
//#see https://developer.wordpress.org/themes/functionality/internationalization/
'menu_name' => __( 'Docs', 'your-textdomain' ),
],
'show_in_graphql' => true,
'hierarchical' => true,
'graphql_single_name' => 'document',
'graphql_plural_name' => 'documents',
] );
} );

Next.js: How do you pass data to a route created dynamically

I have a component that is receiving data:
const ProductTile = ({ data }) => {
let { productList } = data
var [products] = productList
var { products } = products;
return (
<div>
<div className="p-10 grid grid-cols-1 sm:grid-cols-1 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-3 gap-5">
{products.reduce((products, product) => products.find(x => x.productId === product.productId) ? products : [...products, product], []).map(({ colorCode, defaultColorCode, now, productId, productCode, productDescription, }, index) => {
return (
<Link key={`${productId}${index}`}
href={{
pathname: '/s7-img-facade/[slug]',
query: { slug: productCode },
}}
passHref>
/* template */
</Link>
)
})}
</div>
</div>
)
}
export default ProductTile
It creates a grid of templates each wrapped in a <Link> component which is rendering a dynamic component;
/s7-img-facade/[product]
What I would like is for the dynamic component to have access to products object which is in the ProductTile .
I know that I can do a getStaticProps in the dynamic component to do another request but that seems redundant and not dry...
Any ideas how the dynamic component get access to the products object?
Thanks in advance!
You've got the right idea - you can pass additional properties in the query field, but you'll need to use getServerSideProps to extract those from the query param and pass it to the page component as props. Something like this:
// pages/product.js
...
<Link key={`${productId}${index}`}
href={{
pathname: '/s7-img-facade/[slug]',
query: {
description: productDescription,
slug: productCode
},
}}
passHref
>
/* template */
</Link>
...
// pages/s7-img-facase/[slug].js
export default function S7ImgFacasePage({ description }) {
return <p>{ description }</p>
}
export const getServerSideProps = ({ params }) => {
const description = { params }
return {
props: {
description
}
}
}
So basically you pass it from the first page in params, read that in getServerSideProps of the second page, and then pass that as a prop to the second page component.
You mentioned getStaticProps - this won't work with static pages because getStaticProps is only run at build time so it won't know anything about the params you send at run time. If you need a fully static site, then you should consider passing it as a url parameter and reading it in useEffect or passing all possible pages through getStaticPaths and getStaticProps to generate all your static pages.

gatsby-source-medium thumbnail image not showing

I'm using gatsby in my react project, to show my medium, articles inside the project.
below is my graphql query for that.
const BlogPost = () => {
const blogMediumQueryData = useStaticQuery(graphql`
query Medium {
allMediumPost(sort: { fields: [createdAt], order: DESC }) {
edges {
node {
id
title
uniqueSlug
createdAt(formatString: "MMM YYYY")
virtuals {
previewImage {
imageId
}
}
author {
name
}
}
}
}
}
`)
const blogs = blogMediumQueryData.allMediumPost.edges
return (
<Blog
image={blog.node.virtuals.previewImage.imageId}
title={blog.node.title}
date={blog.node.createdAt}
author={blog.node.author.name}
path={blog.node.uniqueSlug}
/>
)
this gives me the preview image ID. And I'm passing it to the child component as a prop. But when I try to show the image with the Img component from gatsby, the Image is not showing.
Here is my code for the child component
import React from "react"
import { Link } from "gatsby"
import { slugify } from "../utils/utilityFunctions"
import Image from "../elements/image"
const Blog = ({ image }) => {
return (
<div className="content-block">
<div className="post-thubnail">
{image && (
<Link to={postUrl} target='blank'>
<Image src={image} alt={title} />
</Link>
)}
</div>
)
}
export default Blog
Here is the code for the Image component
import React from "react";
import Img from "gatsby-image";
const NonStretchedImage = props => {
let normalizedProps = props
normalizedProps = {...normalizedProps.fluid, aspectRatio: 1}
let alignment;
if(props.align === 'right'){
alignment = '0 0 0 auto'
} else if(props.align === 'left'){
alignment = '0 auto 0 0'
}else{
alignment = '0 auto'
}
if (props.fluid && props.fluid.presentationWidth) {
normalizedProps = {
...props,
style: {
...(props.style || {}),
maxWidth: props.fluid.presentationWidth,
margin: alignment,
},
}
}
return <Img {...normalizedProps} />
}
export default NonStretchedImage;
This is my first project with gatsby and graphql. Is there are anything that I have missed or is there anything that I'm doing wrong?
Thanks in advance
A few caveats that I guess will put you on the track to fix the issue.
node, in the GraphQL query is an array, in the same way, I guess that virtuals it is. Check and test the response in the localhost:8000/___graphql playground.
So assuming that your query works as expected, your code should look like:
const BlogPost = () => {
const blogMediumQueryData = useStaticQuery(graphql`
query Medium {
allMediumPost(sort: { fields: [createdAt], order: DESC }) {
edges {
node {
id
title
uniqueSlug
createdAt(formatString: "MMM YYYY")
virtuals {
previewImage {
imageId
}
}
author {
name
}
}
}
}
}
`)
const blogs = blogMediumQueryData.allMediumPost.edges
return (
<Blog
image={blog.node[0].virtuals.previewImage.imageId}
title={blog.node[0].title}
date={blog.node[0].createdAt}
author={blog.node[0].author.name}
path={blog.node[0].uniqueSlug}
/>
)
Alternatively, you can loop through the array of nodes and use your previous Blog component since it will get each iterable variable.
I don't think your Image component be able to render a gatsby-image only using the imageId. Gatsby needs a bunch of data (given by its transformers and sharps) to render the image, not using an identifier but series of fields (that's why it usually renders query fragments, noted by ...). Your image component, in the end, should render something like:
<img src={`https://medium.com/${blog.node[0].virtuals.previewImage.imageId}`}
Based on: https://blog.devgenius.io/how-to-scrap-your-medium-articles-with-gatsby-js-f35535ebc09d
So summarizing, gatsby-source-medium by itself doesn't provide enough data to use gatsby-image or gatsby-image-plugin plugins so I'm afraid you won't be able to use the Img component. You have to use the standard img tag.

Resources