The Issue
I cannot query the API by slug, it must be by id. An KeystoneJS headless CMS provide the data via API and my NextJS should use this data in a static generated Next.js app.
Keystone API must be queried like this:
All Posts: (ALL_POSTS_QUERY)
query {
allPosts {
id
slug
title
}
}
Single Post: (POST_QUERY)
query {
Post(where: { id: $id }) {
title
body
}
}
I do use Apollo Client to connect to the API endpoint.
A query for an individual post must be formatted as described above, with an id variable and that's what seems to be the issue. I need to generate static pages by slug and not by id.
The functions
getStaticPaths()
export const getStaticPaths = async () => {
const { data } = await apolloClient.query({
query: ALL_POSTS_QUERY,
});
return {
paths: data.allPosts.map(({ id, slug }) => ({
params: { slug: slug },
})),
fallback: false,
};
};
getStaticProps()
export const getStaticProps = async ({ params }) => {
const id = params.id;
const { data } = await apolloClient.query({
query: POST_QUERY,
variables: {
id,
},
});
return {
props: {
term: data.Post,
},
};
};
More info about KeystoneJS generated APIs.
Please help
I'm very new to developing so my understanding of this is still basic. Apologies if I've misunderstood the logic. Please can anyone help me with where I'm going wrong with my functions? I wasn't able to find anyone else trying to build dynamic routes by slug but querying the API by id to retrieve that post's data.
Your problem is that you want to access the prop id of params, but params.id simply does not exist. What exists is params.slug. If you want to pass through the id, then change your code to this:
export const getStaticPaths = async () => {
const { data } = await apolloClient.query({
query: ALL_POSTS_QUERY,
});
return {
paths: data.allPosts.map(({ id, slug }) => ({
params: { id },
})),
fallback: false,
};
};
Now you are passing through the id instead of the slug and should be fine.
Related
I am using nextjs to build a directory. I effectively want to click on 'more info' and an info page to load under the URL of /info/[id]-[first_name]-[last_name].
I am pulling data from an api by id, which will then get the first_name and last_name data.
I have a file inside an info folder named [id]-[first_name]-[last_name] :
export default function Info({ info }) {
return (
<div>
<h1>First Name</h1>
<p> Last Name </p>
</div>
);
}
export const getStaticPaths = async () => {
const res = await fetch('http://xxx:1337/api/info');
const data = await res.json();
// map data to an array of path objects with params (id)
const paths = [data].map(info => {
return {
params: [{
id: `${info.id}-`,
first_name: `${info.first_name}-`,
last_name: `${info.last_name}`
}]
}
})
return {
paths,
fallback: false
}
}
export const getStaticProps = async (context) => {
const id = context.params.id;
const res = await fetch('http://xxxx:1337/api/info/' + id);
const data = await res.json();
return {
props: { info: data }
}
With this I just get the error:
Error: A required parameter (id]-[first_name]-[last_name) was not provided as a string in getStaticPaths for /info/[id]-[first_name]-[last_name]
I guess that error is pretty self-explanatory, but I am blocked at this point. I have seen that i may be able to use a slug, but that means re-working a lot of the api.
Any direction with this is apprecated. Thanks!
in this way you can catch all attributes /info/[id]/[first_name]/[last_name]
by making the file /info/[...slug]
export const getStaticProps = async ({ query }) => {
const [id ,firstname ,lastname] = query.slug }
or keep it /info/[slug] and get it as string after that you can split it
In my project I'm using NextJs and tRPC for backend calls. I wanted to fetch some data in getServerSideProps using tRPC and provide it in Page component, also using react-query state for whole application. Here's my _app withTRPC config
export default withTRPC<AppRouter>({
config({ ctx }) {
const url = `${getBaseUrl()}/api/trpc`;
return {
url,
transformer: superjson,
queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
};
},
ssr: false,
})(MyApp);
I used ssr: false because of 'bug' in NextJs, which will cause to return empty props for first render, if set to true.
Here's my getServerSideProps function on the page
export const getServerSideProps = async () => {
const ssg = createSSGHelpers({
router: appRouter,
ctx: await createContext(),
transformer: superjson,
});
const entryRD = await getPageBySlug(option.some("trados"))();
await ssg.prefetchQuery("contentful.getProductList");
console.log("==>props", entryRD, ssg.dehydrate());
return {
props: {
trpcState: ssg.dehydrate(),
entryRD,
},
};
};
When I log to the console on server, both values are there, entryRD and ssg.dehydrate(). The latter contains object with mutations and queries and also data with correctly fetched data. Here is my page code:
const Page = ({ entryRD, trpcState }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
const { data, isLoading } = trpc.useQuery(["contentful.getProductList"]);
console.log("==>data", data, isLoading);
return isLoading ? <div>Loading...</div> : <EntryCompontn entry={entryRD} />
When I read the docs, I understand it like:
fetch data on server,
use ssg.dehydrate() to return cache to component
when you use trpc.useQueried(), it will return cached value from state
Unfortunately data is empty and isLoading is true for a brief moment, when data is fetched. Did I misunderstood something, or did I make a mistake?
I am fetching all posts from the backend from the API call http://localhost:3000/api/jobs and it is working perfectly by using getstaticProps(). Now I want to get a particular post based on slug and my API call is http://localhost:3000/api/jobs/:slug and it perfectly working API call But whenever I used the code shown below for dynamic routes it shows me a server error on the frontend and unable to fetch particular post data.
[slug].js
export const getStaticPaths = async () => {
const res = await fetch(`${API}/jobs`);
const post = await res.json();
const paths = post.map(job => {
return {
params: { slug: job.slug }
}
})
return {
paths,
fallback:true
}
}
export const getStaticProps = async (ctx) => {
const slug = ctx.params.slug;
const [job, photo] = await Promise.all([
fetch(`${API}/jobs/${slug}`).then(r => r.json()),
`${API}/jobs/photo/${slug}`
]);
if (!job) {
return {
notFound:true
}
}
return {
props: {
job,
photo
},
revalidate:60
}
}
Also whenever I used another API call like http://localhost:3000/api/jobs-edit which has the same function as that of http://localhost:3000/api/jobs inside getStaticPaths() then it performs well and gives us single post data.
What can be the problem?
I have a Next page that uses next-i18next in a getServerSideProps and I have another page that uses getServerSideProps to pull data from MongoDB. Both work correctly.
I would like to be able to add next-i18next to the function that connects to Mongo (basically combine the getServerSideProps functions), but I'm getting the error:
nexti18n-next Error: Initial locale argument was not passed into serverSideTranslations'
The first page's getServerSideProps function that connects to next-i18n
export const getServerSideProps = withAuthUserSSR({ whenUnauthed: AuthAction.REDIRECT_TO_LOGIN,})(async ({ locale, }) => {
return {
props: {
...(await serverSideTranslations(locale,
[
'language-file',
...
]
)),
},
};
})
The getServerSideProps function in the second page that pulls data from Mongo:
export const getServerSideProps = withAuthUserSSR({ whenUnauthed: AuthAction.REDIRECT_TO_LOGIN })(async (context) => {
const username = context.params.var[0];
const userId = context.params.var[2];
const { db } = await connectToDatabase();
const pipeline = [
...
]
const postdata = await db.collection('posts').aggregate(pipeline).toArray();
return {
props: {
userId,
username,
postdata: JSON.parse(JSON.stringify(postdata)),
},
};
})
Is it possible to 'add' the next-i18next code to the second function? It seems to me to be an issue with the different way 'locale' and 'context' are defined in each function. I have tried lots of combinations of both but end up messing up either the mongo query or the translations.
This is how I thought it would be done:
export const getServerSideProps = withAuthUserSSR({ whenUnauthed: AuthAction.REDIRECT_TO_LOGIN })(async (context,{ locale, }) => {
const username = context.params.var[0];
const userId = context.params.var[2];
const { db } = await connectToDatabase();
const pipeline = [
...
]
const postdata = await db.collection('posts').aggregate(pipeline).toArray();
return {
props: {
...(await serverSideTranslations(locale,
[
'language-files',
...
]
)),
userId,
username,
postdata: JSON.parse(JSON.stringify(postdata)),
},
};
})
Many thanks for any possible help!
I have a product page at /products/[slug].js
and I use Incremental Static Generation for a wordpress/graphql site:
export async function getStaticProps(context) {
const {params: { slug }} = context
const {data} = await client.query(({
query: PRODUCT_SLUG,
variables: { slug }
}));
return {
props: {
categoryName: data?.productCategory?.name ?? '',
products: data?.productCategory?.products?.nodes ?? []
},
revalidate: 1
}
}
export async function getStaticPaths () {
const { data } = await client.query({
query: PRODUCT_SLUGS,
})
const pathsData = []
data?.productCategories?.nodes && data?.productCategories?.nodes.map((productCategory) => {
if (!isEmpty(productCategory?.slug)) {
pathsData.push({ params: { slug: productCategory?.slug } })
}
})
return {
paths: pathsData,
fallback: true,
}
}
Everything works as expected except one thing. If I delete a product from wordpress which was previously published, NextJs serves the cached page instead of showing 404 - Not found page, and I think this is how it is supposed to work, meaning that if something isn't rebuilt, show the previous (stale) page.
But how can I completely remove the cache for a specific product which has been deleted and it is not fetched again from the PRODUCT_SLUGS query ?
I have read the fallback options: true, false, blocking but none of them seems to work.
Is there a solution to this, either a next.config.js configuration or another work around ?
So I ran into this same issue, although I am using GraphCMS. So here is what you need to do to fix:
export async function getStaticProps(context) {
const {params: { slug }} = context
const {data} = await client.query(({
query: PRODUCT_SLUG,
variables: { slug }
}));
if (!data) {
return {
notFound: true
}
} else {
return {
props: {
categoryName: data?.productCategory?.name ?? '',
products: data?.productCategory?.products?.nodes ?? []
},
revalidate: 1
}
}
}
You need to return notFound: true in getStaticProps
notFound - An optional boolean value to allow the page to return a 404 status and page.
See this page in the docs https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
Then in getStaticPaths change fallback to fallback: "blocking". If you keep fallback: true it is going to serve the stale page since that built successfully.
I think this is possible starting from next#12.1.x using this feature On-demand Incremental Static Regeneration
https://nextjs.org/blog/next-12-1#on-demand-incremental-static-regeneration-beta
basically you can define an api path in this way
// pages/api/revalidate.js
export default async function handler(req, res) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
const PRODUCT_SLUGS = req.query.product_slug;
try {
await res.unstable_revalidate(`/products/${PRODUCT_SLUGS}`)
return res.json({ revalidated: true })
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating')
}
}
Using this api path you can invalidate the cache for a specific product