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

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.

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.

Creating relative URLs with `new URL()` behaves differently when first param is a variable. WHY?

I'm trying to implement web workers in NextJs, I've followed their example but It really bugs me that I cannot pass the worker relative URL as a variable to new URL(url, baseUrl).
The following snippet is where the worker gets called:
import { useEffect, useRef, useCallback } from 'react'
export default function Index() {
const workerRef = useRef()
useEffect(() => {
const workerUrl = '../worker.js';
console.log({
URL: new URL('../worker.js', import.meta.url),
meta: import.meta.url
});
console.log({
URL: new URL(workerUrl, import.meta.url),
meta: import.meta.url
});
workerRef.current = new Worker(new URL('../worker.js', import.meta.url))
workerRef.current.onmessage = (evt) =>
alert(`WebWorker Response => ${evt.data}`)
return () => {
workerRef.current.terminate()
}
}, [])
const handleWork = useCallback(async () => {
workerRef.current.postMessage(100000)
}, [])
return (
<div>
<p>Do work in a WebWorker!</p>
<button onClick={handleWork}>Calculate PI</button>
</div>
)
}
This strangely logs:
{
"URL":"/_next/static/media/worker.3c527896.js",
"meta":"file:///home/omar/CODE/NextJs/lullo/with-web-worker-app/pages/index.js"
}
{
"URL":"file:///home/omar/CODE/NextJs/lullo/with-web-worker-app/worker.js",
"meta":"file:///home/omar/CODE/NextJs/lullo/with-web-worker-app/pages/index.js"
}
How in the world is this any different:
const workerUrl = '../worker.js';
console.log({
URL: new URL('../worker.js', import.meta.url),
meta: import.meta.url
});
console.log({
URL: new URL(workerUrl, import.meta.url),
meta: import.meta.url
});
The problem is that I cannot pass the URL as a prop, to some generic worker caller. I get the annoying error:
SecurityError: Failed to construct 'Worker': Script at 'file:///home/omar/CODE/NextJs/lullo/client/src/utils/WebWorkers/postErrorToServer.ts' cannot be accessed from origin 'http://localhost:3000'.
This is probably happening because in the first case:
const workerUrl = '../worker.js';
const url = new URL(workerUrl, import.meta.url);
webpack sees the URL as dynamic and is unable to properly bundle the web worker at compile-time. Something similar happens if you define the worker as follows:
const url = new URL('../worker.js', import.meta.url);
const worker = new Worker(url);
This comment on a discussion in webpack's GitHub repo might help in your case. I don't think the worker URL can be truly dynamic, due to the above reason - webpack needs to know the url of the worker script at compile-time.

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

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})}>

Meteor server-render with Routes and Meteor.userId

I yould like use server-render package on my Meteor App, using React and React-Router v4. But, onPageLoad event return an error :
Error running template: TypeError: Meteor.subscribe is not a function
It's on the createContainer function :
export default createContainer(() => {
const postsHandle = Meteor.subscribe('post');
const loading = !postsHandle.ready();
const postsList = Post.find({ 'draft': false }).fetch();
const listExists = !loading && !!postsList;
return {
loading,
listExists,
posts: listExists ? postsList : [],
};
}, PostList);
I don't understand how i can get Meteor.subscribe on null if i'm on the server.
Anyone have idea about my problem ?
Thank you community !
I faced the same error, it is because as mentioned in the comment, there is no 'Meteor.subscribe' on the server, so when the code gets passed onto the server via the sink object, it raises an error.
You should wrap your subscriptions in a if(Meteor.isClient), even if your client code is running on ONLY the client.
Other issues can also occur, say suppose if you are trying to read the screen.width, etc type of properties, you would have to wrap it just like above
So your code becomes
export default createContainer(() => {
var loading,listExists = false
var postsList= []
if(Meteor.isClient){
const postsHandle = Meteor.subscribe('post');
loading = !postsHandle.ready();
postsList = Post.find({ 'draft': false }).fetch();
listExists = !loading && !!postsList;
}
return {
loading,
listExists,
posts: listExists ? postsList : [],
};
}, PostList);
This should work fine, just take care of the variables and the functional scopes

Resources