I have a problem figuring out how to localize my content with Next.js and Strapi.
my current file structure looks like so:
pages/
blog/
- [post].tsx
- portfolio.tsx
Inside portfolio I am fetching data for all posts created on Strapi, depending on which locale is currently set, like so:
export const getStaticProps = async ({ locale }) => {
const res = await fetch(
`https://strapi.com/api/articles?locale=${locale}&populate=*`
);
const data = await res.json();
return {
props: {
articles: data.data,
},
};
};
In [post] it looks like that:
export const getStaticPaths = async ({ locales, locale }) => {
const res = await fetch(
`https://strapi.com/api/articles?locale=${locale}&populate=*`
);
const data = await res.json()
const ids = data.data.map((post: any) => post.id);
const paths = ids
.map((id: any) =>
locales.map((locale: any) => ({
params: { post: id.toString() },
locale,
}))
)
.flat();
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context: any) => {
const id = context.params.post;
const res = await fetch(
`https://strapi.tincors.com/api/articles/${id}?populate=*`
);
const res_blocks = await fetch(
`https://strapi.tincors.com/api/articles/${id}?populate[blocks][populate]=*`
);
const data = await res.json();
const blocks_data = await res_blocks.json();
const block_data_slider = blocks_data.data.attributes.blocks[0].files.data;
return {
props: { article: data.data, slider: block_data_slider },
};
};
Note that each article on Strapi has different id for it's localized version - so as an example:
article_1_pl has id of 10
but it's english variant has id of 12.
fun fact - on portfolio.tsx data is fetched successfully, and post miniature cards are properly displaying based on the current domain (on dev I am using two different hosts for i18n - localhost:3000 for PL & example.com:3000 for EN).
However once I try to redirect myself to the full article, by clicking on the post miniature card, I get the 404 page not found error in the browser, with these errors poping each second in the console logs Error.
It doesn't matter which local host I am currently at.
cards are wrapped in a <Link href="/blog/${id}" locale={route.locale} />
However the moment I change my code in [post].tsx by removing the "locale" from the endpoint:
const res = await fetch(
`https://strapi.com/api/articles?locale=${locale}&populate=*`
);
to:
const res = await fetch(
`https://strapi.com/api/articles?populate=*`
);
suddenly each of my PL articles on localhost:3000 are displayed properly, and only the english variants aren't working (404 page not found).
I assume that it is due to the fact, that by removing the "locale" from the API endpoint it only fetches the polish articles, not the english ones, but it baffles me why it's not working at all, when I use the localized endpoint source.
How I want my app to work:
I want article description (which is generated as a dynamic route) to display in active language, by fetching localized data by the post id.
My question is:
How do I fetch the proper localized data from Strapi in [post].tsx
when using getStaticPaths & getStaticProps. What is wrong with above code and how do I fix it?
I apologize if the above description is too chaotic to understand - feel free to ask for more explanations if necessary :)
Related
Been experimenting with some server components and nextjs (v13) and have encountered something that i'm not sure how to do. Let's say in theory I wanted to have something with this functionality (with the requests running serverside)
const ClientComponent = () => {
// this value cannot be hardcoded into the server component, nor can we change
// this value at any point in time during the clients components life (just using const for example)
// in reality it'd be set in some other format
const id = "some-id"
return <ServerComponent id={somethingId} />
}
const fetchData = async (id: string) => {
// writing this off top of head, not sure if correct syntax but some basic post request using the id
const res = await fetch("someUrl", { data: id });
const data = await res.json();
return { data };
}
const ServerComponent = async ({ id }: { id: string }) => {
if (!id) return null;
const { data } = await fetchData(id);
return (
<div>
{data.someValue}
</div>
);
}
How would I go about doing something of this nature? is this considered "improper" / "bad practice"? if so what would be a better way to go about doing this? Keep in mind that ClientComponent could be three-four nodes down (with each node being a client component)
Thanks :)
I have a problem that my website being deployed to Vercel with NextJS ISR. When I use getStaticPaths. The website will reload unlimited when I click to getStaticPaths that page.
Only getStaticProps will not make this error. I am using the hobby plan. Everything works when I am on localhost and use "npm run build and start".
here is my code:
export async function getStaticProps({ params: { slug } }) {
const res = await fetch(`${API_URL}/post/${slug}`);
const posts = await res.json();
return {
props: {
posts,
},
// Next.js will attempt to re-generate the page:
revalidate: 10, // In seconds
};
}
export async function getStaticPaths() {
// this URL is created specifically for fetch first time of ISR
// My first post will unlimited reload**********
const res = await fetch(`${API_URL}/postforisrfirst`);
// const res = await fetch(`${API_URL}/post`);
const posts = await res.json();
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { slug: post.slug },
}));
return { paths, fallback: "blocking" };
}
In SingleBlogPost.jsx i have:
export async function getStaticPaths() {
const res = await fetch("http://localhost:1337/api/posts");
let { data } = await res.json();
const paths = data.map((data) => ({
params: { slug: data.attributes.slug },
}));
return {
paths,
fallback: "blocking",
};
}
where I generate blog pages by their slug.
But then in getStaticProps I need to fetch single post by slug but I want to do it by id.
export async function getStaticProps(context) {
console.log("context", context);
const { slug } = context.params;
console.log("slug is:", slug);
const res = await fetch("http://localhost:1337/api/posts");
const { data } = await res.json();
return {
props: {
data,
},
revalidate: 10, // In seconds
};
}
And I want to keep url like /blog/:slug , I dont want to include id. in url .When I already fetch all posts in getStaticPaths how I can access post id in getStaticProps to avoid fetching by slug?
You can filter your API response by your slug to get the same result
const res = await fetch(`http://localhost:1337/api/posts?filters[slug][$eq]${slug}`);
This will generate your desired result
It looks like recently released a workaround using a file system cache.
The crux of the solution is that they save the body object in memory, using something like this:
this.cache = Object.create(null)
and creating methods to update and fetch data from the cache.
Discussion here: https://github.com/vercel/next.js/discussions/11272#discussioncomment-2257876
Example code:
https://github.com/vercel/examples/blob/main/build-output-api/serverless-functions/.vercel/output/functions/index.func/node_modules/y18n/index.js#L139:10
I found a concise work around that uses the object-hash package. I basically create a hash of the params object and use that to create the tmp filename both on set and get. The tmp file contains a json with the data I want to pass between the two infamous static callbacks.
The gist of it:
function setParamsData({params, data}) {
const hash = objectHash(params)
const tmpFile = `/tmp/${hash}.json`
fs.writeFileSync(tmpFile, JSON.stringify(data))
}
function getParamsData (context) {
const hash = objectHash(context.params)
const tmpFile = `/tmp/${hash}.json`
context.data = JSON.parse(fs.readFileSync(tmpFile))
return context
}
We can then use these helpers in the getStaticPaths and getStaticProps callbacks to pass data between them.
export function getStaticPaths(context) {
setParamsData({...context, data: {some: 'extra data'})
return {
paths: [],
fallback: false,
}
}
export function getStaticProps(context) {
context = getParamsData(context)
context.data // => {some: 'extra data'}
}
I'm sure someone can think of a nicer API then re-assigning a argument variable.
The tmp file creation is likely not OS independent enough and could use some improvement.
So, I am working on a simple Next.Js app. It's just a Lyrical Page but I'm running onto some issues. I'm new to Next.Js and this is my first project. So, here is my problem:
I have a dynamic set of pages/folders which looks like this:
songs (empty folder)
[singer] (child of songs containng just an index.jsx)
[title] (child of [singer] containing another index.jsx)
Now in my [title] - index.jsx im rendering a simple page providing lyrics for a specific song.
My problem is that I want to count views (every time someone opens this page) for each song sepertly. I have the following code:
export const getStaticProps = async (context) => {
const res = await fetch(`${process.env.PROXY}api/lyrics/post/${encodeURIComponent(context.params.title)}`);
const data = await res.json();
const send = await fetch(`${process.env.PROXY}api/lyrics/views/${data.post._id}`);
return {
props: {
data: data.post,
message: 200
}
}
}
export const getStaticPaths = async () => {
const res = await fetch(`${process.env.PROXY}api/lyrics/getAll`);
const data = await res.json();
const paths = data.all.map((name) => ({ params: { singer: name.singer.toString(), title: name.title.toString() } }));
return {
paths: paths,
fallback: 'blocking'
}
}
The problem is I know getStaticProps renders only on build time, however, I want to render every time so that I can count the views with my send variable.
Can someone please help me figure this out? Any help will be appreciated!
Today I came across one issue in Next.js api. I wanted to fetch data from my internal API in getStaticProps, but as documentation says you should not fetch your local API in getStaticProps and instead you should import the function. But what if I wanted to send a pageIndex to my local API and fetch the specific page. I cant do req.body.page here, so how do I get the data ?
my API looks like this
export const getData = async (page) => {
const data = await fetch(
`https://rickandmortyapi.com/api/character?page=${page}`
);
const response = await data.json();
return response;
};
export default async function getCharactersAPI(req, res) {
//here I would like to get the data that of the page (something like req.body.page)
//and do something like const data = await getData(req.body.page)
const data = await getData();
res.status(200).json(data);
}
and my fetch code looks like this
export const getStaticProps = async pageContext => {
//here I would like to put the page I want to fetch in the getCharactersAPI()
//and do something like const query = await getCharactersAPI(3), meaning page number 3
const query = await getCharactersAPI();
const response = await query.json();
return {
props: { response },
};
};
The pageContext contains the URL parameters, which presumably has the page number that you want to fetch?
So extract the info from the pageContent then call your query.