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.
Related
Problem:
An entire field of my MongoDB's collections' is not transmitted correctly from the db to the react client. For an exemple, the first element of this field is tweet_id: 1537466989966413825 in my DB. It becomes tweet_id: 1537466989966413800 when I fetch from my client.
My steps
I pull my data from the MongoDB using their App services' function like that:
exports = function(){
var collection = context.services.get("mongodb-atlas").db("meme_news").collection("news");
return collection.find({});
};
When I press the Run button of their built-in terminal, the correct data is displayed.
On my react's application, I perform the fetch like that:
useEffect(() => {
getData();
}, []);
const getData = async () => {
let getAllData = await user.functions.getAllData();
// all these data are wrong
let tweetId = getAllData
.map((ele) => {
return ele.tweet_id;
})
let tweetIdFirstEle = tweetId[0];
// this return 1537466989966413800
// It should return 1537466989966413825
};
Why is my async/await altering my Mongodb data? I have no idea what is going on here.
I am new in Nextjs, i am trying to integrate [slug.js] page, i want to know that how can we manage/get data in sidebar (similar blogs) ? in other words for blog details i used "get static path" and "props", But now i want to pass "current slug" ( to API) so i can fetch all blogs with this blog category,How can i do this ?
Client-side approach:
Since you pass the post as page-props via getStaticProps, you can either take the slug from there (if it's included in your data model), or extract the slug from the url via next's useRouter hook in case you want to do client-side fetching:
import axios from "axios"; // using axios as an example
import { useRouter } from "next/router";
const Component = () => {
const [similarPosts, setSimilarPosts] = useState([]);
const router = useRouter();
const { slug } = router.query;
const getSimilarPosts = async () => {
if (!router.isReady() || !slug) return [];
const { data } = await axios.get("/api/similar-posts-route/" + slug);
return data;
};
useEffect(() => {
if (similarPosts.length > 0) return;
(async () => {
const posts = await getSimilarPosts(); // assuming API returns an array of posts as data.
setSimilarPosts(posts);
})();
}, []);
return <div>Similar posts: {JSON.stringify(similarPosts)}</div>;
};
[...]
Server-Side approach (preferred):
I believe it would be a better approach to directly fetch similar posts inside getStaticProps to reduce API calls and for a better UX.
Inside getStaticProps you can take the slug from context.params and fetch all similar posts directly from your database/CMS, and pass them directly as props to your page component:
export async function getStaticProps({ params }) {
const { slug } = params;
// fetch similar posts directly from the database using the slug (don't call the API, it's not up yet during build phase)
const similarPosts = await executeDatabaseQueryForSimilarPosts(slug);
// [...] fetch the rest of the page props
return {
props: {
similarPosts,
// [...] return the rest of page props
},
revalidate: 60 * 30 // re-fetch the data at most every 30 minutes, so the posts stay up to date
};
}
// directly take all similar posts from props
const Component = ({similarPosts}) => {
return <div>Similar posts: {JSON.stringify(similarPosts)}</div>;
};
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 :)
I am trying to get some user data from stripe in the getServerSideProps function in Next.JS so that I can pass it down to some other components but I am having a hard time obtaining the data from the getServerSideProps function.
Below is my getServerSideProps function:
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps(context) {
const user = getSession(context.req, context.res).user
const resources = await table.select({}).all()
const customer = await fetch(`/api/customers`).then(res => res.json()).then(data => {
data.data.find(user_data => user_data.metadata['auth0_user_id'] === user.sub);})
const subscriber_status = await customer.metadata['subscription_status'] === 'true';
return {
props: {
tech_resources: minifyRecords(resources),
subscriber_stats: subscriber_status, // I want to return the subscriber status so that I can pass it to another component later on
}
}
}
});
Below is my original fetch request which obtains the data I am looking for If I use it as a standalone function or with a useEffect hook.
fetch(`/api/customers`).then(res => res.json()).then(data => {
const customer = data.data.find(user_data => user_data.metadata['auth0_user_id'] === user.sub);
if (customer.metadata['subscription_status'] === 'true') {
// Do Something;}}
However, when I tried this inside of getServerSideProps it does not seem to work. Can anyone help me out on this?
The await fetch then using the promise looks strange to me.
When you use await, then your result will be the response.
So maybe try
const response = await fetch(`/api/customers`);
const data = await response.json();
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.