Is it possible that you prevent Next.js getStaticPaths for generating static pages for a specific locale?
In my next config:
i18n: {
locales: ['default', 'en', 'hu', 'de', 'cz', 'eu', 'sl'],
defaultLocale: 'default',
localeDetection: true,
},
That's just because we need the locale all the time, and by the default, Next.js does not support it, so we have to use the middleware trick followed by the official Next.js docs: https://github.com/vercel/next.js/discussions/18419
But now when I want to generate sites, I don't want to generate pages like
/default/products/id1
/default/products/id2
How can I prevent Next.js doing this? Because this does not work:
For my faq page where /help/[maincategory]/[subcategory]/[help].
Using slice (I will skip the default locale):
locales.slice(1).map((locale) => {
pages.map((item) => {
item.data.mainTabList?.[locale]?.map((main, mainidx) => {
main.subTabList?.map((sub, subidx) => {
questions.map((help) => {
help.data.helplist?.[locale]?.map((title) => {
const urllink = {
maincategory: urlConverter(main.tab),
subcategory: urlConverter(sub.tab),
help: title.url
}
routes.push(urllink)
})
})
})
})
})
})
const paths = routes.map((doc) => ({
params: {
maincategory: `${doc.maincategory}`,
subcategory: `${doc.subcategory}`,
help: doc.help?.toLowerCase(),
},
}))
Can anyone help me how to solve this, that /default pages won't get generated, because that's just a hacky way for my locale prefix, we won't use it anywhere.
It's possible to control which locales getStaticPaths will generate paths for by returning the desired locales in the paths array.
From the i18n Dynamic Routing documentation:
For pages using getStaticProps with Dynamic Routes, all locale
variants of the page desired to be prerendered need to be returned
from getStaticPaths. Along with the params object returned for paths,
you can also return a locale field specifying which locale you want to
render.
In your case, your getStaticPaths function should roughly look like the following.
export const getStaticPaths = ({ locales }) => {
// Your own logic
// Filter out the `default` locale, and map through the remaining locales
locales.filter((locale) => locale !== 'default').map((locale) => {
pages.map((item) => {
item.data.mainTabList?.[locale]?.map((main, mainidx) => {
main.subTabList?.map((sub, subidx) => {
questions.map((help) => {
help.data.helplist?.[locale]?.map((title) => {
const urlLink = {
maincategory: urlConverter(main.tab),
subcategory: urlConverter(sub.tab),
help: title.url,
locale // Also push current `locale` value to be used in `paths` array
}
routes.push(urlLink)
})
})
})
})
})
})
const paths = routes.map((doc) => ({
params: {
maincategory: `${doc.maincategory}`,
subcategory: `${doc.subcategory}`,
help: doc.help?.toLowerCase(),
},
locale: doc.locale // Pass `locale` value here
}))
return {
paths,
fallback: false
}
}
Related
I'm using Strapi as a CMS, where I query for slugs, and I would like to have statically generated pages using getStaticPaths and getStaticProps in Next.js.
As I need to work with multiple locales, I have to map through the locales and get paths for each "Announcements" I'm getting from my query.
The error message I'm getting is:
Error: A required parameter (slug) was not provided as a string in getStaticPaths for /updates/announcements/[slug]
This is my getStaticPaths:
export async function getStaticPaths({ locales }: any) {
const paths = await (
await Promise.all(
locales.map(async (locale: any) => {
const { data } = await client.query({
query: gql`
query Announcements {
announcements(locale: "${locale}") {
data {
attributes {
slug
locale
}
}
}
}
`,
});
return {
announcements: data.announcements.data,
locale,
};
})
)
).reduce((acc, item) => {
item.announcements.map((p: any) => {
acc.push({
params: {
slug:
p.attributes.slug === "/" ? false : p.attributes.slug.split("/"),
},
locale: p.attributes.locale,
});
return p;
});
return acc;
}, []);
return {
paths,
fallback: false,
};
}
If I console.log(paths) I get the following in the terminal:
[
{ params: { slug: [Array] }, locale: 'en' },
{ params: { slug: [Array] }, locale: 'en' },
{ params: { slug: [Array] }, locale: 'en' },
{ params: { slug: [Array] }, locale: 'da' },
{ params: { slug: [Array] }, locale: 'sv' },
{ params: { slug: [Array] }, locale: 'nb' }
]
I might think that Next.js don't want the slug to be an array, but I'm not entirely sure. What am I doing wrong?
You page uses dynamic routes named (/updates/announcements/[slug]), therefore the param slug is required in paths.
From the Next.js getStaticPaths documentation:
The paths key determines which paths will be pre-rendered. For example, suppose that you have a page that uses Dynamic Routes named pages/posts/[id].js. If you export getStaticPaths from this page and return the following for paths:
return {
paths: [
{ params: { id: '1' }},
{
params: { id: '2' },
// with i18n configured the locale for the path can be returned as well
locale: "en",
},
],
fallback: ...
}
Then, Next.js will statically generate /posts/1 and /posts/2 during next build using the page component in pages/posts/[id].js.
The slug param can only be a string since it's used to generate routes. As you found when logging paths, you were trying to pass slug: [Array].
The problem in the question's code snippet is this expression to assign a slug:
// ...
params: {
slug: p.attributes.slug === "/" ? false : p.attributes.slug.split("/"), // 👈
},
// ...
This expression will either assign false (boolean) or an array of substrings (see the docs for String.prototype.split()).
In this case, as confirmed in a comment above, simply passing the slug as a string solves the issue.
The confusion likely came from following a tutorial that uses an optional catch-all route (pages/[[...slug]]) instead of regular dynamic routes (pages/[slug]) (ref).
From the Next.js getStaticPaths documentation again:
If the page name is pages/posts/[postId]/[commentId], then params should contain postId and commentId.
If the page name uses catch-all routes like pages/[...slug], then params should contain slug (which is an array). If this array is ['hello', 'world'], then Next.js will statically generate the page at /hello/world.
If the page uses an optional catch-all route, use null, [], undefined or false to render the root-most route. For example, if you supply slug: false for pages/[[...slug]], Next.js will statically generate the page /.
I have this JavaScript data file(src/test/test.js):
module.exports = {
"title": "...",
"Number": "number1",
"Number2": ({ number1 }) => number1 / 2,
}
I want to pass this file verbatim(functions preserved) to a page, so that the page can use that data to build itself. I already have the page template and everything else sorted out, I just need to find a way to pass this into the page.
The first approach I tried is requireing this file in gatsby-node.js and then passing it as pageContext.
gatsby-node.js
const path = require('path');
exports.createPages = ({actions, graphql}) => {
const { createPage } = actions;
return graphql(`
query loadQuery {
allFile(filter: {sourceInstanceName: {eq: "test"}}) {
edges {
node {
relativePath
absolutePath
}
}
}
}
`).then(result => {
if (result.errors) {
throw result.errors;
}
for (const node of result.data.allFile.edges.map(e => e.node)) {
const data = require(node.absolutePath);
createPage({
path: node.relativePath,
component: path.resolve('./src/templates/test.js'),
context: data,
});
}
});
};
gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `test`,
path: `${__dirname}/src/test/`,
},
},
],
}
src/templates/test.js
import React from 'react';
const index = ({ pageContext }) => (
<p>{pageContext.Number2()}</p>
);
export default index;
However, I get this warning when running the dev server:
warn Error persisting state: ({ number1 }) => number1 / 2 could not be cloned.
If I ignore it and try to use the function anyway, Gatsby crashes with this error:
WebpackError: TypeError: pageContext.Number2 is not a function
After searching for a while, I found this:
The pageContext was always serialized so it never worked to pass a function and hence this isn't a bug. We might have not failed before though.
- Gatsby#23675
which told me this approach wouldn't work.
How could I pass this data into a page? I've considered JSON instead, however, JSON can't contain functions.
I've also tried finding a way to register a JSX object directly, however I couldn't find a way.
Regarding the main topic, as you spotted, can't be done that way because the data is serialized.
How could I pass this data into a page? I've considered JSON instead,
however, JSON can't contain functions.
Well, this is partially true. You can always do something like:
{"function":{"arguments":"a,b,c","body":"return a*b+c;"}}
And then:
let func = new Function(function.arguments, function.body);
In this case, you are (de)serializing a JSON function, creating and casting a function based on JSON parameters. This approach may work in your scenario.
Regarding the JSX, I guess you can try something like:
for (const node of result.data.allFile.edges.map(e => e.node)) {
const data = require(node.absolutePath);
createPage({
path: node.relativePath,
component: path.resolve('./src/templates/test.js'),
context:{
someComponent: () => <h1>Hi!</h1>
},
});
}
And then:
import React from 'react';
const Index = ({ pageContext: { someComponent: SomeComponent} }) => (
return <div><SomeComponent /></div>
);
export default index;
Note: I don't know if it's a typo from the question but index should be capitalized as Index
In this case, you are aliasing the someComponent as SomeComponent, which is a valid React component.
I have a page [categories][price].js and im trying to achieve the data structure in getStaticPaths e.g
cat1/10 cat1/20 cat1/30 cat1/40 cat2/10 cat/20 etc
I have looked at this post: Next.js: getStaticPaths for nested dynamic routes as it's the same error but as their data structure is a bit different I'm not sure how to translate this to my example
It looks like im mapping the data incorrectly as I get the following error when trying to create my dynamic routes.
Error: Additional keys were returned from `getStaticPaths` in page "/[catSlug]/[price]". URL Parameters intended for this dynamic route must be nested under the `params` key, i.e.:
return { params: { catSlug: ..., price: ... } }
Keys that need to be moved: 0, 1.
How can I correctly map my data?
[categories][price].js
export async function getStaticPaths() {
const prices = [' 10', ' 20', ' 30']
const categories = [{ name: 'cat' }, { name: 'cat2' }, { name: 'cat3' }]
const paths = categories.map(({ slug }) =>
prices.map((price) => ({ params: { catSlug: slug, price: price } }))
)
return {
paths,
fallback: false
}
}
flatten the array
const paths = categories.map(({ name }) =>
prices.map((price) => ({ params: { catSlug: name, price: price } }))
).flat()
I'm Using Drupal 8 as a headless CMS with Gatsby generating static pages.
In Drupal I have set up some node Types (Article, Image, other...). All entities have brand and category term relationships.
category is a single selection and brand is a multiple selection
Gatsby creates pages from my template on URL's like.
http://example.com/category-1/brand-1/
http://example.com/category-1/brand-2/
http://example.com/category-2/brand-1/
http://example.com/category-2/brand-2/
The articles and image nodes with the terms selected will display on the corresponding url's.
The problem I'm having is since I changed the brand term to a multi selection, some URL's are not being created.
Because of field_brand[0].name Gatsby will only create the URL for the first selection of brand on the article node.
// gatsby-node.js
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions;
const pageTemplate = path.resolve(`src/templates/pageTemplate.js`);
return graphql(`
{
taxonomyTermBrand {
field_colours
name
}
allNodeImage {
nodes {
relationships {
field_image_drop {
uri {
url
}
}
}
}
}
allNodeArticle {
nodes {
body {
processed
}
relationships {
field_brand {
name
}
field_category {
name
}
}
}
}
}
`, { limit: 1 }).then(result => {
if (result.errors) {
throw result.errors
}
result.data.allNodeArticle.nodes.forEach(node => {
const brand_path = node.relationships.field_brand[0].name;
const category_path = node.relationships.field_category.name;
createPage({
path: `${category_path}/${brand_path}`,
component: pageTemplate,
context: {
brandName: node.relationships.field_brand[0].name,
categoryName: node.relationships.field_category.name,
},
})
})
})
}
Essentially the value of taxonomyTermBrand.name is the same as node.relationships.field_brand[0].name when the data is passed to the template, but I can't use taxonomyTermBrand.name because the path in createPage is in the allNodeArticle.forEach()
Is there a better aproach or another way to set the paths and display the tagged content on those pages?
I think you'd be able to do a forEach over each of the field_brand terms for each of the articles.
Something like:
result.data.allNodeArticle.nodes.forEach(node => {
const brands = node.relationships.field_brand;
const category = node.relationships.field_category.name;
brands.forEach(brand => {
createPage({
path: `${category}/${brand.name}`,
component: pageTemplate,
context: {
brandName: brand,
categoryName: category,
},
})
});
})
I'm learning ES6 syntax, as well as the latest Apollo libs. This withData code is adapted from the Githunt-React Apollo demo.
const withData = graphql(GETIMS_QUERY, {
options: ({ params }) => ({
variables: {
"fromID": Meteor.userId(),
"toID": `${params.toID}`,
},
}),
});
params doesn't seem to contain what I expect. I'd like to insert a breakpoint in order to examine the contents of params. But if I add a breakpoint next to options, I find that params is undefined.
I guess I may need to add a breakpoint inside this code block in order to see the contents of params:
const withData = graphql(GETIMS_QUERY, {
options: ({ params }) => ({
//IS THERE A WAY TO ADD A BREAKPOINT IN HERE SOMEHOW?
//MAYBE RETURN `VARIABLES` AS A FUNCTION RESULT?
variables: {
"fromID": Meteor.userId(),
"toID": `${params.toID}`,
},
}),
});
Is there a way to do that?
Thanks in advance to all for any info.
You can call console.log (and you can add a breakpoint on that line) and return the object explicitly:
const withData = graphql(GETIMS_QUERY, {
options: ({ params }) => {
console.log(params);
return {
variables: {
"fromID": Meteor.userId(),
"toID": `${params.toID}`,
},
};
},
});