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
Related
I'm fairly new to developing web apps, I started learning react + redux toolkit while using Django as a backend framework
to my point,
I was trying to minimize calls to the server by using a useEffect to check if the value of a specified selector is filled with data, so then I can use that data instead of calling the server again
now when I make the check
useEffect(() => {
flights.value.length <= 0 && dispatch(fetchFlights())
// eslint-disable-next-line
}, [])
it works when you first call the component
but after that, every time I open that component (whether I click on its link, or using a navigate(-1) to go back to that component) it won't display anything. I'll need to manually refresh the page for it to work correctly
this is for the component to render the data via a map function (works as it displays it when first calling it)
{!logged ? <Login /> : flights.loading ? <div>loading..</div> : flights.value.length > 0 && flights.value.map(...)}
now if i change the useEffect to this:
useEffect(() => {
dispatch(fetchFlights())
// eslint-disable-next-line
}, [])
basically without the data check, it works just fine
I was wondering if there is a way to check for the data and have it displayed without a call to the server again
or hear your thoughts about calling the server again and again and maybe its just better that way?
If you are using redux-toolkit, createApi feature is the best option. You can use the fetched data across your app without retrieving it multiple times or refresh the obtained data based on your needs (polling, caching, manual refetching, invalidating it after a certain time... )
// Need to use the React-specific entry point to allow generating React hooks
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
// Define a service using a base URL and expected endpoints
export const fligthsApi = createApi({
reducerPath: 'flights',
baseQuery: fetchBaseQuery({ baseUrl: 'https://yourapi.com' }),
endpoints: (builder) => ({
getFlights: builder.query({
query: () => `/yourFlightsPath`,
}),
}),
})
// Export hooks for usage in function components, which are
// auto-generated based on the defined endpoints
export const { useGetFligthsQuery } = fligthsApi
The you can use it in your app like:
export default function App() {
// Even if this component is unmount, flights data will be cached
const { data, error, isLoading } = useGetFligthsQuery()
// render UI based on data and loading state
}
(This is a minimal example, complete working code needs importing the api in your store)
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,
}
);
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
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;
}
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)
}