As of Next.js 10, the getStaticPaths
function returns an object that must contain the very important fallback
key as documented at: https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required
While the documentation is precise, it is quite hard to digest for someone that is just beginning with Next.js, could someone try to provide a simpler or more concrete overview of those options?
fallback: 'blocking' The paths returned from getStaticPaths will be rendered to HTML at build time by getStaticProps . The paths that have not been generated at build time will not result in a 404 page. Instead, Next. js will SSR on the first request and return the generated HTML .
If a page has Dynamic Routes and uses getStaticProps , it needs to define a list of paths to be statically generated. When you export a function called getStaticPaths (Static Site Generation) from a page that uses dynamic routes, Next. js will statically pre-render all the paths specified by getStaticPaths .
You should use getStaticProps if: The data required to render the page is available at build time ahead of a user's request. The data comes from a headless CMS. The page must be pre-rendered (for SEO) and be very fast — getStaticProps generates HTML and JSON files, both of which can be cached by a CDN for performance.
Next. js is a JavaScript framework created by Zeit. It lets you build server-side rendering and static web applications using React. It's a great tool to build your next website. It has many great features and advantages, which can make Nextjs your first option for building your next web application.
How to test
First of all, when testing things out to make sure I had understood them, I was getting really confused because when you run in development mode (next dev
) the behavior is quite different than when running in production mode (next build && next start
), as it is much more forgiving to help you develop quickly. Notably, in development, getStaticPaths
gets called on every render , so everything always gets rendered to their latest version, which is unlike production where more caching might be enabled.
The docs describe the production behavior, so to test things out, you really need to use production mode.
The next issue is that I couldn't easily find an example where you can create and update pages from inside the example itself to easily view their behavior. I finally ended up doing that at: https://github.com/cirosantilli/node-express-sequelize-nextjs-realworld-example-app while porting the awesome Realworld example project, which produces a simple multiuser blog website (mini Medium clone).
With those tools in hand, I was able to confirm what the docs say. This answer was tested at this commit which has Next.js 10.2.2.
fallback: false
This one is simple: only pages that are generated during next build
(i.e. returned from the paths
property of getStaticPaths
) will be visible.
E.g., if a user creates a new blog page at /post/[post-id]
, it will not be immediately visible afterwards, and visiting that URL will lead to a 404.
That new post will only become visible if you re-run next build
, and getStaticPaths
returns that page under paths
, which is the case for the typical use case where getStaticPaths
returns all the possible [post-id]
.
fallback: true
With this option, Next checks if the page has been pre-rendered to HTML under .next/server/pages
.
If it has not:
Next first quickly returns a dummy pre-render with empty data that had been created at build time.
In this, you are expected to tell the user that the page is loading.
You must handle that case, or else it could lead to exceptions being thrown due to missing properties.
The way to handle this is described in the docs by checking router.isFallback
:
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
if (router.isFallback) {
return <div>Loading...</div>
}
return <div>post.body</div>
}
So in this example, if we hadn't done the router.isFallback
check, post would be {}
, and doing post.body
would throw an exception
After the actual page finishes rendering for the first time with data (the data is fetched with getStaticProps
at runtime), the user's browser gets automatically updated to see it, and it stores the resulting HTML under .next/server/pages
If the page is present under .next/server/pages
however, either because:
next build
Next.js just returns it, without rendering again.
Therefore, If you edit the post, it will not re-render the page cache. The outdated page will be returned at all times, because it is already present under .next/server/pages
, so next does not re-render it.
You will have to re-run next build
to see updated versions of the pages.
Therefore, this is not what you generally want for the multi-user blog described above. This approach is generally only suitable for websites that don't have user-generated content, e.g. an e-commerce website where you control all the content.
fallback: true
: what about pages that don't exist?
If the user accesses a page that does not exist like /post/i-dont-exist
, Next.js will try to render it just like any other page, because it checks that it is not in .next/server/pages
thinks that it just hasn't been rendered before.
This is unlike fallback: false
, where Next.js never generates new pages at runtime, and just returns a 404 direction.
In this case, your code will notice that the page does not exist when getStaticProps
queries the database, and then you tell Next.js that this is a 404 with notFound: true
as mentioned at: How to return a 404 Not Found page and HTTP status when an invalid parameter of a dynamic route is passed in Next.js? so Next.js renders a 404 page and caches nothing.
fallback: 'blocking'
This is quite similar to fallback: true
, except that it does not return the dummy loading page when a page that hasn't been cached is hit for the first time
Instead, it just makes the browser hang, until the page is rendered for the first time.
Future requests to that page are quickly served from the cache however, just like fallback: true
.
https://dev.to/tomdohnal/blocking-fallback-for-getstaticpaths-new-next-js-10-feature-1727 mentions the rationale for this, it appears to break certain rather specific features, and is generally not what you want unless you need one of those specific features.
Note that Next.js documentation explicitly states that in fallback: true
, it detects crawlers (TODO how exactly? User agent or something else? Which user agents), and does not return the loading page to crawlers, which would defeat the purpose of SSR. https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required mentions:
Note: this "fallback" version will not be served for crawlers like Google and instead will render the path in blocking mode.
so there doesn't seem to be a huge advantage for SEO purposes in using 'blocking'
over true
.
However, if your user is a security freak and disables JavaScript, they will only see the loading page. And are you sure the Wayback machine won't show the loading page? What about wget
? Since I like such use cases, I'm tempted to just use fallback: 'blocking'
everywhere.
revalidate
: Incremental Static Regeneration (ISR)
When revalidate
is given, new requests to a page that is in the .next/server/pages
cache also make the cache be regenerated. This is called "Incremental Static Regeneration".
revalidate: n
means that our server will do at most 1 re-render every n
seconds. If a second request comes in before the n
seconds, the previously rendered page is returned and a new re-render is not triggered. So large n
means users see more outdated pages, but less server workload.
A large re validate could therefore help the server handle large traffic peaks by caching the reply.
This is what we have to use if we want website users to both publish and update their own posts:
fallback: true
or fallback: 'blocking'
revalidate: <integer>
revalidate
does not make much sense with fallback: false
.
When revalidate: <number>
is given, behavior is as follows:
if the page is present under .next/server/pages
, return this prerendered immediately, possibly rendered with outdated data.
At the same, also kickstart a page rebuild with the newest data.
When the rebuild is finished, the target page won't be automatically updated to the latest version. The user would have to refresh the page to see the updated version.
otherwise, if the page is not cached, do the same that true
or 'blocking'
would do, by either returning a dummy wait page, or blocking until it gets done, and create the cached page
After a page is built by either of the above cases (first time or not), if it gets accessed again in the next number
seconds, do not trigger rebuilds. This way, if a very large number of users is visiting the website, most of the requests won't require expensive server render work: we will do at most one re-render every number
seconds.
SSR for a single request (i.e. ignore revalidate
) so that users can see the results of their blog page edits
If for example:
they first see the outdated version of the post. Then redirected this visit would trigger a rebuilt with the new data they've provided in the edit page, and only after that finishes and the user refreshes they would see the updated page.
So this behavior is also not ideal UI behavior for the editor, as the user would be left thinking:
What just happened, was my edit not registered?
for a few seconds.
This can be solved with "preview mode" which is documented at: https://nextjs.org/docs/advanced-features/preview-mode It was added in Next.js 12. Preview mode checks if come cookies are set, and if so makes getStaticProps
rerun regardless of revalidate
, just like getServerSideProps
.
However, even preview mode does not solve this use case super nicely, because it does not invalidate/update the cache, which is a widely requested thing, related:
so it could still happen that the user visits the page without cache and sees the outdated page. I could work around this by removing the cookies and making a an extra GET request, but this produces an useless get request and adds more complexity.
I learned about this after opening an issue about it at: https://github.com/vercel/next.js/discussions/25677 thanks to @sergioengineer for pointing it out.
Related threads:
SSR vs ISR: per user-login-based information
ISR is an optimization over SSR. However, like every optimization, it can increase the complexity of the system.
For example, suppose that users can "favorite" blog posts.
If we use ISR, it only makes much sense to pre-render a logged off page, because it only makes sense to pre-render the stuff that is common for multiple users.
Therefore, if we want to show to the user the information:
Have I starred this page yet or not?
then we have to do a second API request and then update the page state with it.
While it may sound simple, this adds considerable extra complexity to the code in my experience.
With SSR however, we could simply check the login cookies sent by the user as usual, and fully render the page perfectly customized to the current user on the server, so that no further API requests will be needed. Much simpler.
So you should really only do it if you benchmark it and it is worth it.
Here's an example of checking login cookies: https://github.com/cirosantilli/node-express-sequelize-nextjs-realworld-example-app/blob/8dff36e4bcf659fd048e13f246d50c776fff0028/back/IndexPage.ts#L23 That sample setup uses the exact same SWR tokens that are being used to make JavaScript API requests but also via cookies. We don't have to worry about XSS in that demo because we only use login on GET requests. All modifying requests like POST are done from JavaScript exclusively, and don't authenticate from cookies.
The ISR dream: infinite revalidate
+ explicit invalidation + CDN hooks
As of Next.js 12, ISR is wonky for such a CRUD website, what I would really want is for things to work as follows:
This approach would really lead to ultra-fast page loads and minimal server workload Nirvana.
I think Vercel, the company behind Next.js, might such a CDN system running on their product, but I don't see how to nicely use an arbitrary CDN of choice, because I don't see such hooks. I hope I'm wrong :-)
But just the explicit invalidation + infinite revalidate would already be a great thing to have even without the CDN hook system.
whenever we want to implement ISR
or SSG
techniques in dynamic routes
, we are supposed to pass the paths,that we want to be statically generated at the build time,
to getStaticPaths
function .Although , in some situations we might have new paths that are not returned by getStaticPaths
and we have to handle this paths with fallback property that is also returned from getStaticPaths
Next.js official docs
.
false : new paths will result in a 404 page
true :
new path will be statically generated (getStaticProps
is called) - loading state is shown while generating page(via router.isFallback
and showing fallback page) - page is rendered with required props after generating - new path will be cached in CDN (later requests will result in cached page) - crawler Bots may index fallback page (not good for Seo)
"blocking" :
new path will be waiting for HTML to be generated (via SSR
) - there will be no loading state(no fallback page) - new path will be cached in CDN (later requests will result in cached page)
NOTE : after Next.js 12 the fallback:true
in ISR
technique wont be showing fallback page to crawler Bots Read more
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With