Next.js: getStaticProps not updating fetch values in production - javascript

I'm basically developing a blog on Next.js. Because it is another team which is in charge of the back-end, I'm currently making fetch API calls from getStaticProps to get my articles even though it's better practice to make database queries directly:
export async function getStaticProps({ params, res }) {
try {
const result = await fetch(`${API.baseURL}/get_article/${params.id}`);
const article = await result.json();
return {
props: { article },
};
} catch (error) {
res.statusCode = 404;
return { props: {} };
}
}
While this works perfectly in development mode, getting the article, editing it, and then accessing it again does not work in production (even locally, with the built version).
I guess it has something to do with Next.js handling cache somehow... What am I doing wrong? Thank you!

First of all the argument of function getStaticProps i.e the context object doesn't have any property named res. So res.statusCode = 404; doesn't do anything here.
And getStaticProps is meant be used for static site generation, additionally for dynamic routes, you can export another function getStaticPaths which should generate and return an array of paths with the dynamic route params for which getStaticProps will be called at build time for pre-rendering the pages.
In development mode, data-fetching methods will be called per-request basis so your code works. But in production mode, it will show the pre-rendered static pages which means the page will show the content as it was rendered and if you edit and update the content it will not reflect on the pages.
If you decide to go with static-site-generation either you have to rebuild the entire site after an update to a blog or you have to have some kind of client-side data-fetching logic that will update the blog when you update its content.
For client-side data-fetching you can use something like swr or react-query
Here is some psuedo-code which might help with pre-rendering the pages,
for route /article/[articleId]
export async function getStaticPaths() {
const articles = await /* db query to get the list of articles or fetch from remote API*/
// generate a list of paths with route params
const paths = articles.map(article => ({ params: { articleId: article.id }}))
return {
paths,
fallback: false
// fallback can be true if you want to show a fallback version of page
// and serve JSON for unknown articles
}
}
export async function getStaticProps(ctx) {
try {
const result = await fetch(`${API.baseURL}/get_article/${params.id}`);
const article = await result.json();
return {
props: { article },
};
} catch (error) {
return {
props: null
}
}
}
Learn more about how fallback works in returned value of function getStaticPaths docs.
Another alternative is to use getServerSideProps as the data-fetching method which will be called on each request for the page, but TTFB(time to first byte) will be higher. So for a blog-post site, I will not suggest using getServerSideProps.

You have to add the revalidate parameter. Find out more here.
In your case
export async function getStaticProps({ params, res }) {
try {
const result = await fetch(`${API.baseURL}/get_article/${params.id}`);
const article = await result.json();
return {
props: { article },
revalidate: 10 // 10 seconds
};
} catch (error) {
res.statusCode = 404;
return { props: {} };
}
}
Please note that revalidate parameter.

There is a way to update the HTML that is generated using getStaticProps, this process is called incremental server regeneration. This will ensure that your page is updated whenever you push an update to your blog post.
NextJS has documented this
https://nextjs.org/docs/basic-features/data-fetching
You search for ISR on the above page to understand how it's done
Basically you'll have to specify a time after which NextJS will try to update the page and in case there is a new post altogether, it will be server rendered on first request and then cached, once cached it'll work almost like a static page, refer fallback: 'blocking'
Quoting from website :
Next.js allows you to create or update static pages after you’ve built your site. Incremental Static Regeneration (ISR) enables you to use static-generation on a per-page basis, without needing to rebuild the entire site. With ISR, you can retain the benefits of static while scaling to millions of pages.
Consider our previous getStaticProps example, but now with Incremental Static Regeneration enabled through the revalidate property

Related

Next.js rendering strategy for unique dynamic route

I want to make the best use of the server where my Next.js web app will be hosted, even if it is at the cost of the APIs where users get informations.
So I was wondering what was the best approach to render uniques dynamic routes, for example: /post/[postId].
I want to avoid SSR and have static HTML files hydrated by APIs as often as possible, as I've made for /home/[page] where I've done some ISR to avoid frequent rerenders like this:
export async function getStaticProps(context = {}) {
return {
props: {},
revalidate: 120, //cache page for 120s
}
}
// No prerender of paths <=> "paths: []"
export async function getStaticPaths(context = {}) {
return {
paths: [],
fallback: 'blocking'
}
}
The problem for /post/[postId] being that postId is a unique identifier so caching the page has no real interest and prerendering is not possible.
The thing is /post/id1 and /post/id2 have no real HTML differences because the [postId] property is only used in a useEffect to fetch data so a SSR is a complete waste of server ressources..
So the question is what could be a way to optimise Next.js rendering uniques dynamics routes ? Any idea is welcomed !
I guess one way is to use dynamic imports. This will decrease your bundle size and introduce lazy loading for your JavaScript code. One note with static HTML pages is that they are small in size so not a lot of optimization is needed.
const SomePage = dynamic(
() => import('#modules/some-page/index'),
{
ssr: false,
}
);

Statically pre-render most popular queries with NextJS [duplicate]

This question already has an answer here:
How to add new pages without rebuilding an app with +150k static pages?
(1 answer)
Closed 10 months ago.
I am using NextJS to pre-render pages of the format:
./pages/folder/[variable].js
I have a set of some ~ 8000 possible values for [variable], so it is not practically feasible to statically generate all 8000 of these pages. Because of this, I use getSeverSideProps() whenever this page is loaded. Here is what that looks like:
export async function getServerSideProps(context) {
const { variable } = context.params
const { res } = context;
// Caches response for 15 minutes on the server side
res.setHeader('Cache-Control', `s-maxage=900, stale-while-revalidate`)
const { data } = await axios.get(url + variable);
return { props : {data} }
}
In spite of there being over ~ 8000 values, the top 50 values contain ~90% of queries.
Is there any way to statically pre-render these 50 values with something like getStaticPaths() and getStaticProps(), such that these particular 50 queries are faster to load?
Secondarily, and less concerning, is that the list of the most common requests changes slightly over time. If there was a solution to dynamically determine this list of 50 values, that would be ideal (although is not strictly necessary).
This is exactly the concept of Incremental Static Regeneration in Nextjs:
Next.js allows you to create or update static pages after you’ve built your site. Incremental Static Regeneration (ISR) enables you to use static-generation on a per-page basis, without needing to rebuild the entire site. With ISR, you can retain the benefits of static while scaling to millions of pages.
and here is how it works:
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: 'blocking' }
}
Don't forget that you still need to define getStaticProps function on your page.
Here is the official Documentation about Incremental Static Regeneration

Nextjs getInitialProps blocked the page rendering in client side?

Since I like to add SSR to my upcoming project to improve SEO, I would like to try out next. What I want is that only use SSR for the initial page, and the rest of navigations in the site will be client side rendering. I see the getInitialProps fit the most in this case, accordingly the documentations.
As my understanding, getInitialProps is run in server for the initial page rendering, and is run in the browser when navigating using next/link. The issue I found is that the getInitialProps seems to block the page rendering. (i.e. page changed/rendered after getInitialProps is completed)
import axios from 'axios'
function Posts(props) {
return (
<div>
<div>Posts:</div>
<div>
{JSON.stringify(props)}
</div>
</div>
)
}
Posts.getInitialProps = async (context) => {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
// Wait longer to see the effect
// await (new Promise((resolve) => {
// setTimeout(resolve, 5000)
// }))
return {
props: {
posts: response.data
}
}
}
export default Posts;
How can I do it like in pure React, render the jsx first, then fill in the props? (the execution JSON.stringify(props) might be ignored at first)
Also, in next 9.3, the team introduced getServerSideProps, which is recommended over getInitialProps. How can they be comparable when they are not the same that getServerSideProps will on run in server?
Based on your comments, you want to do the fetch on the server, on the initial page load. However, if navigating between pages you don't want rendering to block while waiting for getInitialProps to return.
One solution is to check if you're on the server, and do the fetch in getInitialProps. If on the client, don't do the fetch in getInitialProps and instead fetch using useEffect in your render method.
import {useEffect} from 'react'
import axios from 'axios'
const isServer = () => typeof window === 'undefined'
const getPosts = () => {
return axios.get('https://jsonplaceholder.typicode.com/posts')
.then(response => response.data)
}
function Posts({posts}) {
const [renderPosts, setRenderPosts] = useState(posts)
useEffect(() => {
if(posts === null) {
getPosts()
.then(setRenderPosts)
}
}, [])
return (
<div>
<div>Posts:</div>
<div>
{JSON.stringify(renderPosts)}
</div>
</div>
)
}
Posts.getInitialProps = async (context) => {
if(isServer()) {
return {
posts: await getPosts(),
}
}
else {
return {
posts: null,
}
}
}
export default Posts
By the way, you may be tempted to use getServerSideProps here, since it is only called if rendering on the server. However, when a page using getServerSideProps is rendered, it will actually make a call to the server to get data from getServerSideProps, even if you're navigating using next/link. From the Next.js 9.3 blog post:
When navigating between pages using next/link instead of executing getServerSideProps in the browser Next.js will do a fetch to the server which will return the result of calling getServerSideProps.
This would still cause the blocking issue you're wanting to avoid.
One final note, this might not be an idiomatic solution. There may be a more "standard" solution. I just wasn't able to find one. You could likely also use a wrapper around your page component that could do all of this in a more consistent way. If you use this pattern a lot, I'd recommend that.

How can I use getInitialProps only during the NextJS site build?

When using NextJS to build a static site, I would like the getInitialProps method to fire only during the build step and not on the client.
In the build step, NextJS runs the getInitialProps method before each component's rendered HTML is used to generate the page's static HTML. On the client, NextJS also runs this method before the page component is rendered in order to return the necessary props for the component. Thus, large requests can delay the client's first paint as this is a blocking request.
// example usage of API call in getInitialProps
import fetch from 'isomorphic-unfetch'
function Page({ stars }) {
return <div>Next stars: {stars}</div>
}
Page.getInitialProps = async ({ req }) => {
const res = await fetch('https://api.github.com/repos/zeit/next.js')
const json = await res.json()
return { stars: json.stargazers_count }
}
export default Page
I'm unwilling to move my slow API request to componentDidMount in order to avoid the blocking request because I want to use the data returned during the build step to populate the static HTML, and this particular request doesn't need to be dynamic or updated after the build.
Is there a way I can make getInitialProps run only when next export builds and not as the client loads the page?
Is this good practice?
I found the workaround with NextJs 9.0.3 (other versions may also work, I didn't test that)
// XXXPage is your page
XXXPage.getInitialProps = async (req) => {
if (process.browser) {
return __NEXT_DATA__.props.pageProps;
}
// original logic
}
For version 9.3 or newer, it's recommended that you use getStaticProps for providing static build props.
export async function getStaticProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
Old answer
There are two ways is one way that I've found to prevent code in getInitialProps from running on a page component load.
1. Use a regular anchor tag without next/link to that page.
getInitialProps only runs when the page is linked from a next/link component. If a regular JSX anchor click me is used instead, the component's getInitialProps will not be invoked. Direct page loads to NextJS static site pages will not invoke getInitialProps.
Note that using a standard anchor instead of the next/link component will cause a full page refresh.
Because this is a poor solution, I've submitted a feature request.
2. Use req in the context argument to conditionally make the API call in getInitialProps.
I believe what #evgenifotia wanted to convey is that req is undefined in a site that's been exported.
// example usage of API call in getInitialProps
import fetch from 'isomorphic-unfetch'
function Page({ stars }) {
return <div>Next stars: {stars}</div>
}
Page.getInitialProps = async (ctx) => {
const { req } = ctx // context object: { req, res, pathname, query, asPath }
if (req) { // will only run during the build (next export)
const res = await fetch('https://api.github.com/repos/zeit/next.js')
const json = await res.json()
return { stars: json.stargazers_count }
}
return {}
}
export default Page
For more information about getInitialProps, see the documentation. One example there confirms that req is expected to only be defined on the server (or during the exporting build):
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent`
This second option may work for some scenarios, but not mine where returning an empty result from getInitialProps will affect the component's this.props.
Note:
Shallow routing is not the answer. According to the documentation (see under "Notes" section):
Shallow routing works only for same page URL changes.
A more detailed and updated version as of the accepted answer:
const isInBroswer = typeof window !== 'undefined';
if (isInBroswer) {
const appCustomPropsString =
document.getElementById('__NEXT_DATA__')?.innerHTML;
if (!appCustomPropsString) {
throw new Error(`__NEXT_DATA__ script was not found`);
}
const appCustomProps = JSON.parse(appCustomPropsString).props;
return appCustomProps;
}

Does Gatsby need to rebuild my entire site every time a docpage is updated?

I am creating a Documentation site to hold DocPages for each of my products. Gatsby is building all of my sites as static pages. I have multiple tech writers who are constantly updating and creating new pages. How does Gatsby handle this? Do I have to rebuild my entire site with Gatsby each time something is updated?
gatsby-source-contentful plugin uses the sync API. If you keep the .cache and public folder around the plugin will be able to get only the changed entries and assets and update the Gatsby data. by keeping the .cache should build faster.
depend on you webhook Setup this build process may be triggered many times to workaround that maybe you can add a script that delays the builds and make sure that only one build process is running.
I know it is an old question, but I struggled with this today and it seems noone out there gave a decent post or answer about it, Gatsby v2 still rebuilds everything when you restart gatsby develop. This is a no-go if your build takes 20min on first run. My solution is below.
You could use the cache API to save a hash or updatedAt info of your data, and only call createPage action if the cache doesn't match (the data was updated)
This is how I do it in my project:
exports.createPages = async ({ actions: { createPage }, graphql, cache }) => {
const results = await graphql(`
query GetAllCars {
myNamespaceFromGraphqlSource {
allCars {
id
updatedAt
}
}
}
`);
const createDetailsPage = async (car) => {
const carCache = await cache.get(car.id);
if(carCache != car.updatedAt) {
createPage({
path: `/car_details/${car.id}/`,
component: require.resolve("./src/templates/details.js"),
context: {
id: car.id,
},
});
await cache.set(car.id, car.updatedAt);
}
}
for(let i=0; i < results.data.myNamespaceFromGraphqlSource.allCars.length; i++ ) {
const car = results.data.myNamespaceFromGraphqlSource.allCars[i];
await createDetailsPage(car)
}

Categories

Resources