"article seed # 0" => "article-seed-0" to navigate to other Page with that url? - reactjs

I am routing dynamically with the compilation of static pages using gatsby-node.
const path = require('path');
const slugify = require('slugify');
const templateForContentType = {
articles: 'src/templates/ArticleDetail.tsx',
};
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
.
.
.
result.data.allStrapiFinancialEducationContents.nodes.forEach((node) => {
const contentType = contentTypes[node.content[0].strapi_component];
createPage({
path: `/articles/${slugify(node.title, {
lower: true,
})}`,
component: path.resolve(templateForContentType),
context: {
id: node.id,
},
});
});
};
gatsby compiles that statics files with the .title property
[
{title:"article-seed-0",
{title:"article-seed-1"
]
article-seed-0
...
article-seed-n
when I try to navigate to another page, it forms a url like this:
const {title:"article seed # 0" }
<Link to={article.title}>
this is the result:
Article%20Seed%20#0
the data comes to backend, is there any practical way for any kind of title to a url convention that can be associated with the way gatsby compiles these static files?

Gatsby compiles the static files with the given path:
path: `/articles/${slugify(node.title, {lower: true,})}`,
So, if you are using slugify to convert the title (node.title) to a valid path, it has nothing to do with Gatsby but slugify, which is removing the # to add a hyphen (-) in between among other things (indeed, it's creating a slug from a given string).
If your Link doesn't have the right target URL, just use slugify in the same way:
<Link to={slugify(article.title, {lower:true})}>

Related

Next js Sitemap static export

I need help building a Sitemap for my NextJs project.
I build a headless cms using graphql and next, however, everything is statically generated.
I'm having a lot of issues creating a sitemap. I tried using the npm next-sitemap but all the info I find (youtube and forums) are for projects containing "serversideprops", when my project only contains "getStaticProps" and getStaticPaths. In addition to that I also require the map to handle dynamic paths [slug].js. ** I'm not using typescript
Here is what part of my [slug].js looks like:
> graphql query....
>
> export async function getStaticPaths() { const { posts } = await
> graphcms.request(SLUGLIST); return {
> paths: posts.map((post) => ({ params: { slug: post.slug } })),
> fallback: false, }; }
>
> export async function getStaticProps({ params }) { const slug =
> params.slug; const data = await graphcms.request(QUERY, { slug });
> const { posts } = await graphcms.request(QUERY2); const post =
> data.post; return {
> props: {
> post,
> posts,
> }, }; }
thanks everyone!
next-sitemap also creates the routes based on the static pages you generate. The thing is you have to run it after you generated your project.
Typically, you should have a configuration file like this at the base of your project:
next-sitemap.config.js
/** #type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'https://my-fallback-url.com/',
generateRobotsTxt: true,
trailingSlash: true,
targetDirectory: `${__dirname}/public`,
// Wherever are your pages stored
pagesDirectory: `${__dirname}/src/pages`,
};
and on your package.json
"scripts": {
... other configurations
"postbuild": "next-sitemap"
},
Which will trigger next-sitemap after your build is complete. You should then find all the generated xml files containing your sitemap.

How to add meta tags with gatsby which first need to be fetched

I'm trying to add META tags for Facebook(og-tags). I'm using Gatsby and Helmet. But the problem is that those tags first need to be fetched.
It's a vehicle detail page and I need to show vehicle make and model in those tags, but the vehicle needs first to be fecthed. My code is as follows:
import Helmet from 'react-helmet';
const Page = (props) => {
const [detailsMeta, setDetailsMeta] = useState(undefined);
const resolveVehicleDetailMeta = async () => {
const fetch = require('isomorphic-fetch');
const resolveVehicleImageUrl = (fetchedImage) => {
const parsed = JSON.parse(fetchedImage);
return parsed?.uri
}
const VEHICLE_QUERY = `
query VehicleQuery($reference: String!) {
vehicle (reference: $reference) {
reference
make
model
image
}
}`;
await fetch(`/graphql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: VEHICLE_QUERY,
variables: {
reference: 'some reference'
}
})
})
.then((resp) => resp.json())
.then((result) => {
const vehicle = result?.data?.vehicle;
if(vehicle){
setDetailsMeta({
reference: vehicle.reference,
make: vehicle.make,
model: vehicle.model,
image: resolveVehicleImageUrl(vehicle.image)
})
}
})
.catch((err) => {
console.log('err', err)
});
}
const renderMetaTitle = () => {
const ogTitle = `Tweedehands ${detailsMeta?.make} ${detailsMeta?.model} ${detailsMeta?.reference}`
return ogTitle;
}
return (
<>
<Helmet>
{detailsMeta && <meta property='og:title' content={renderMetaTitle()} />}
...
</Helmet>
The rest...
</>
)
}
And in gatsby config is gatsby-plugin-react-helmet added. The config file is as follows:
const plugins = [
`gatsby-plugin-react-helmet`,
....
]
module.exports = {
developMiddleware: (app) => {
app.use(
'/graphql',
createProxyMiddleware({
target: 'http://localhost:8000'
})
);
},
siteMetadata: {
siteUrl: `https://${settings.DOMAIN}`
},
plugins: plugins
};
Thus, I first fetch data from the server and store it in detailsMeta and then I show it inside Helmet. When I test it on localhost I see those tags and it works fine, but when I test it in Facebook debugger they are not shown.
Can I at all add data to the og-tags which first needs to be fetched and that it be seen by Facebook?
Thanks.
Can I at all add data to the og-tags which first needs to be fetched
and that it be seen by Facebook?
Yes but only if you are using statically analyzed data (i.e: using page queries, static queries, etc). In that case, you just need to add gatsby-plugin-react-helmet plugin in order to add drop-in server-side support to Helmet component.
In your gatsby-config.js:
plugins: [`gatsby-plugin-react-helmet`]
Extracted from https://www.gatsbyjs.com/plugins/gatsby-plugin-react-helmet/
In your case, you are using the fetch method that will be fired on the client-side, so the data won't be statically analyzed hence not present when the Facebook crawler reaches the site. This means that the Helmet component will be populated later than the crawler requests it.
I'm not sure about your specs but you can try converting your fetched into GraphQL nodes in order to use pages queries or static queries fulfill the Helmet component properly.

Next.js : URL with domain.com/category/post

I have trouble understanding the dynamic URL routing to match my use case, and the examples only showcase structure like : domain.com/categories/category1, domain.com/posts/post-1.
As the title says, I would like to have the following URL structures on my blog, with Next.js :
https://www.example.com/category1/post-1
https://www.example.com/category1/post-2
https://www.example.com/category1/post-3
https://www.example.com/category2/post-4
https://www.example.com/category2/post-5
...
I would also like the pages :
https://www.example.com/category1
https://www.example.com/category2
to be accessible.
So far, my file structure looks like this : but I'm getting 404 errors.
Thank you for your help
If that can be useful to someone else, I've found the solution.
The folder structure has to be like this :
pages
[category]
[slug.tsx] // (post page)
index.tsx // (category page)
In the [slug.tsx] page, your static paths must reflect the structure :
export const getStaticPaths: GetStaticPaths = async () => {
const allPosts = await getAllPostsWithSlug();
return {
paths: allPosts?.map((post) => `/${post.category.slug}/${post.slug}`) || [],
fallback: true,
};
};
In the index.tsx (category) page, likewise :
export const getStaticPaths: GetStaticPaths = async () => {
const allCategories = await getAllCategories();
return {
paths: allCategories.map((category) => `/${category.slug}`) || [],
fallback: true,
};
};
With this, I can access my posts with their category in the url.

Gatsby: Creating Pages from Contentful Fields

Each of my posts on Contentful has some associated 'categories' with it. For example, one post might contain:
major: "HCD"
year: "1st Year"
tools: ["R", "Python", "Wordpress"]
These are just fields called major, year etc. with these values but they are treated as individual categories.
On the website, they are displayed as such:
I am trying to create a page for each of these categories. For example, if a user clicks on Photoshop, they should be taken to a page tags/photoshop and all posts containing that tag should be listed out.
Fortunately, I was able to find this guide to help me do this. However, the guide is not for Contentful data so I'm having a bit of trouble on how to do this. I have created the tagsTemplate.jsx and but I'm stuck at creating the actual pages.
For example, this is what I did to try and create pages for tools:
My gatsby-node.js file looks like this:
const path = require(`path`)
const _ = require('lodash');
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type contentfulPortfolioDescriptionTextNode implements Node {
description: String
major: String
author: String
tools: [String]
files: [ContentfulAsset]
contact: String
}
type ContentfulPortfolio implements Node {
description: contentfulPortfolioDescriptionTextNode
gallery: [ContentfulAsset]
id: ID!
name: String!
related: [ContentfulPortfolio]
slug: String!
major: String!
files: [ContentfulAsset]
author: String!
tools: [String]!
year: String!
thumbnail: ContentfulAsset
url: String
contact: String
}
`
createTypes(typeDefs)
}
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
return new Promise((resolve, reject) => {
graphql(`
{
portfolio: allContentfulPortfolio {
nodes {
slug
tools
}
}
}
`).then(({ errors, data }) => {
if (errors) {
reject(errors)
}
if (data && data.portfolio) {
const component = path.resolve("./src/templates/portfolio-item.jsx")
data.portfolio.nodes.map(({ slug }) => {
createPage({
path: `/${slug}`,
component,
context: { slug },
})
})
}
const tools = data.portfolio.nodes.tools;
const tagTemplate = path.resolve(`src/templates/tagsTemplate.js`);
let tags = [];
// Iterate through each post, putting all found tags into `tags`
tags = tags.concat(tools);
// Eliminate duplicate tags
tags = _.uniq(tags);
// Make tag pages
tags.forEach(tag => {
createPage({
path: `/tags/${_.kebabCase(tag)}/`,
component: tagTemplate,
context: {
tag
},
});
});
console.log("Created Pages For" + tags)
resolve()
})
})
}
My tagsTemplate is minimal right now, since I don't know how to query the data:
import React from 'react';
import Layout from "../layouts/Layout"
const Tags = ({ data }) => {
return (
<Layout>
<div>Tags</div>
</Layout>
);
};
export default Tags;
The problem: When I visit the page for one of the tags I know exists (like photoshop), I get a 404. Why are these pages not being created?
What am I doing wrong and how can I fix it? How can this be generalized for three of my 'categories'?
According to what you said in the comments:
I tried console.log(tags) but is shows it as undefined
I did that and it is just a blank space. Does that mean there is nothing in tags at all?
Your contact function looks good, the approach is good since you are adding the tools (list of tags) into a new array to clean it up and leaving unique values (uniq). Once done, you loop through the unique tags and create pages based on that array.
That said, there are a few weak points where your house of cards can fall apart. Your issue start in this line:
const tools = data.portfolio.nodes.tools;
And propagates through the code.
nodes is an array so, to get any value you should do:
const tools = data.portfolio.nodes[0].tools;
To get the first position and so on...
Since tools is never populated, the rest of the code doesn't work.
You can easily fix it looping through the nodes and populating your tags array with something similar to:
const toolNodes = data.portfolio.nodes;
const tagTemplate = path.resolve(`src/templates/tagsTemplate.js`);
let tags = [];
// Iterate through each post, putting all found tags into `tags`
toolNodes.map(toolNode => tags.push(toolNode.tools);
// if the fetched data is still an array you can do toolNodes.map(toolNode => tags.push(...toolNode.tools);
// Eliminate duplicate tags
tags = _.uniq(tags);
// Make tag pages
tags.forEach(tag => {
createPage({
path: `/tags/${_.kebabCase(tag)}/`,
component: tagTemplate,
context: {
tag
},
});
});
console.log("Created Pages For" + tags)

How to source data from multiple directories(file-system) in gatsby using graphQL

I have a directory like this
where I have posts folder which may contain numerous folders and then each video folder may contain some videos.
Now, I have created dynamic pages for the video and video_2 folder using this.
I have two pages now and what I want is to fetch the specific two videos of that directory only in my template page.
what should be my graphQL for this?
Referrence: (For specific content from .md files)
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
}
}
}
`;
This is bringing the specific slug-wise data onto the webpage. What should be the query for sourcing the contents from file-system?(I am using gatsby-source-filesystem).
Hope my question makes sense. Thanks.
So what you're saying is you have a set of directories that should each be turned into a single page. Each page should display the videos that are in the directory.
You could do the following in your gatsby-node; retrieve all files, group them by directory and create pages for each directory.
Note the regex in the query that selects for which files you're interested in.
The createPage call sends the file names videos of the page as context so you might not need to do any further GraphQL query in your page.
See also https://www.gatsbyjs.org/docs/creating-and-modifying-pages/
const path = require("path")
function getDirectoryOfFile(pathName) {
const pathEls = pathName.split(path.sep)
return pathEls[pathEls.length - 2]
}
function groupFilesByDirectory(files) {
return files.reduce((directories, file) => {
const directory = getDirectoryOfFile(file)
directories[directory] = (directories[directory] || []).concat([file])
return directories
}, {})
}
exports.createPages = async function ({ actions, graphql }) {
const { data } = await graphql(`
query {
allFile(filter: { absolutePath: { regex: "/video/" } }) {
edges {
node {
name
absolutePath
}
}
}
}
`)
const files = data.allFile.edges.map(edge => edge.node.absolutePath)
const directories = groupFilesByDirectory(files)
Object.entries(directories).forEach(([directory, videos]) => {
actions.createPage({
path: `/video/${directory}`,
component: require.resolve(/* your page template here */),
context: { videos },
})
})
}
I hope I understood your question right.

Resources