The wait is finally over: Next.js team has just released version 9.3 of their serverless framework, with a series of new features that are surely game-changers for the JAMstack universe.
If you haven't already played with it, we would like to give some pro-tips and practical examples on how to use the new data-fetching primitives that are now available, together with the latest preview mode.
The world as we know it: getInitialProps
A little recap of the former situation. The way to get data from external sources was by exporting an async getInitialProps
function, like so:
export async function getInitialProps({ query: { slug } }) {// slug = 'hello-nextjs'const postContent = await fetch(`https://api.example.com/post/${encodeURIComponent(slug)}`).then(r => r.text())return { postContent };}
Next.js would call this function both server-side, on the first page load, and client-side, for every subsequent visit using the <Link>
tags.
This solution had a couple of problems:
the code in
getInitialProps
had to be isomorphic, meaning that had to work correctly both on the server and on the client (e.g., usingisomorphic-fetch
to call APIs);the function was always called at every page load.
Welcome getStaticProps
and getServerSideProps
!
While keeping retro compatibility with getInitialProps
, Next.js 9.3 introduces new data-fetching primitives. These will improve both developer and user experience by giving a more granular control on the timings of data fetching. Let's dive into them.
getServerSideProps
is very similar to getInitialProps
, meaning that it gets called every time that you load the page, but the code is only executed on the server. This way, you don't have to worry about the execution of data fetching code in both server and browser environments, which have some inconsistencies. In many cases, this increases performance as the server will generally have a faster connection to the data source. It also increases security by exposing less of the data fetching logic.
export async function getServerSideProps(context) {return {props: {}, // will be passed to the page component as props}}
Surely a nice improvement, but wait, the serious news will come with getStaticProps
. The function signature is the same as getServerSideProps
, but the behavior is different.
export async function getStaticProps(context) {return {props: {}, // will be passed to the page component as props}}
getStaticProps
gets called at build time. It won't be called on the client-side, so you can even do direct database queries. Because getStaticProps
runs at build time, it does not receive data that’s only available during request time, such as query parameters or HTTP headers, as it generates static HTML.
When a page with getStaticProps
is pre-rendered at build time, in addition to the HTML file of the page, Next.js generates a JSON file holding the result of running getStaticProps
, so that the output of that function can be immediately available on the CDN to the visitor without additional waiting.
getStaticProps
hence is perfect for cases in which the data required to render the page is available at build time, ahead of a user’s request, and can be publicly cached, meaning that it's not user-specific.
Dynamic routes with getStaticProps
? You need getStaticPaths
!
What happens when you map getStaticProps
with a dynamic route? For example, a blog post? Something like:
pages/blog/article/[slug].js
We've previously mentioned that getStaticProps
is called at build-time, right? OK, so we need a way to provide to Next.js the set of pages for which it will need to create the JSON files containing the results of getStaticProps
. For the Next.js veterans, it's the equivalent of the exportPathMap
function that was called in the Static HTML export.
OK, cool, so now when you have a page that defines getStaticProps
, and the route is dynamic (that is, it has some placeholders), then you should also define a getStaticPaths
function, to specify dynamic routes to be pre-rendered based on data.
export async function getStaticPaths() {return {paths: [{ params: { ... } }],fallback: false};}
We are not going to get in more details for the fallback option, but you can read more for yourself in the Next.js documentation.
The critical point that we want to pass across is that you don't have to pass the complete set of routes that need to be pre-rendered, you can only specify the ones for which you want maximum optimization. For example, you can pre-render only the last ten posts, or the ten most viewed.
By doing so, you are free to decide the best tradeoff between build times and view time.
Alright, but what happens when I'm going to visit one of those articles that are not pre-rendered? Next.js will call getStaticProps
server-side in real-time, generate the JSON and get back to the complete page. From the subsequent visit to that page, for every other visitor, the result will be already cached in the CDN.
It's kind of a lazy-build if you like.
Preview modes: content editors nirvana
What we've seen so far it's all very interesting for developers and useful for visitors... but what about content editors? Next has got something also for them!
Let's suppose we are working on a new article on DatoCMS (or actually on any other headless CMS). Wouldn't it be nice always to be able to see the latest data revision, to have a quick feedback?. Sure it would!
Using the new data fetching methods, we would be tempted to use getServerSideProps
, as it's the one that always give us fresh results. But wait: visitors don't need to fetch content everytime they visit a page, that would be unnecessarily slow; getStaticProps
would be the best option for them! Argh, how do we solve this?
Next.js 9.3 introduces the Preview Mode concept: a way to inform Next.js on how to switch behavior between static and dynamic, depending on a cookie present on the visitor's browser.
How does it work? The first step is to define an API route, only accessible from content editors, that enables the preview mode (maybe passing through authentication first):
// A simple example for testing it manually from your browser.// If this is located at pages/api/preview.js, then// open /api/preview from your browser.export default (req, res) => {res.setPreviewData({})res.end('Preview mode enabled')}
res.setPreviewData
sets some cookies on the browser, which turns on the preview mode. If you use your browser’s developer tools, you’ll notice that the __prerender_bypass
and __next_preview_data
cookies will be set on this request.
Any requests to Next.js containing these cookies will be considered as in preview mode, and a context.preview
flag will be marked as true in getStaticProps
.
export async function getStaticProps(context) {// If you request this page with the preview mode cookies set://// - context.preview will be true// - context.previewData will be the same as// the argument used for `setPreviewData`.}
Now we can update getStaticProps
to fetch different data based on context.preview
and/or context.previewData
.
For example, with a Next.js CMS such as DatoCMS, you have a different API endpoint for draft content. In this instance, you can use context.preview
to modify the API endpoint URL like so:
export async function getStaticProps(context) {// If context.preview is true, append "/preview" to the API endpoint// to request draft data instead of published data. This will vary// based on which headless CMS you're using.const res = await fetch(`https://.../${context.preview ? 'preview' : ''}`)// ...}
That’s it! If you access the preview API route (maybe with a secret and a slug) either from your headless CMS or manually, you should be able to see the preview content. And if you create a new draft revision, you should be able to preview it. Cool!
Next steps with DatoCMS and Next.js
If you want to dive into all the details of 9.3 release, jump in their changelog!
But then, if you want to try out how a Next.js and DatoCMS powered blog works, you can jump ahead with this one-click setup button:
DatoCMS works seamlessly with Next.js, so we covered a lot of common issues and perks of using Next.js in our blog. For further reading, we suggest:
...or you can always read all the details in our documentation.