As I wrote in my blog post titled Learn In Public, I recently brought my attention to learning more about NextJS. Throughout my entire career as a Frontend engineer, I've solely focused on building client-side React applications without much looking at other solutions. NextJS, in the meantime, became very popular and I kept hearing good things about it and when seeing the different use cases of server-side rendering. I can now see myself using it in several projects, or for my potential future business endeavors.
If like me, you started learning NextJS, or need a refresher on the latest features, this article is for you!
Wrapping my head around data fetching
One aspect that struck me the most when I started to learn the framework, was data fetching. NextJS provides different functions to get data asynchronously, and when to use each function can seem tricky at first, as it all depends on different use cases:
- the pre-rendering type desired: server-side rendered or static
- the data: are we loading static or dynamic data? Is that data accessible ahead of a user request
Additionally, you can see this in the title of this post, the names of these functions look quite similar which can be confusing at first, but bear with me, at the end of this article it will be clearer.
Pre-Rendering types with NextJS
As mentioned above, NextJS provides support for server-side rendering (SSR) and static-site generation (SSG) which are both pre-rendering types, and aim for the same result (i.e. better performance, no client-side rendering) but differ in when the HTML pages are being pre-rendered:
- For SSG, the HTML pages are rendered at build time. When you're done building, the result is a set of static files. This is the method I prefer because it allows me to cache my website on a CDN and offers better performance
- For SSR, the site is generated by a server on each request. That means your website is dependent on some code running on a server, not simply static files.
Now let's look at the difference between these two pre-rendering methods when it comes to data fetching
getStaticProps
This function is the first one I encountered when I started to learn NextJS. It's an asynchronous function that fetches the data at build time, which means according to the definitions we just saw above that it's used for SSG.
It takes a context
as an argument (more on that later) and will return an object with a props
field. This prop field contains all the props we'll end up passing down to a page.
This function can be used to :
- call an API / external endpoint and retrieve some data
- read a local file
- query a database
as long as the data to be retrieved is not user-related or a specific entity linked to the client as, again, this function will be triggered at build time. It also means that each time the data changes, we would have to rebuild.
Example showcasing a use case of getStaticProps for a sample /todos API
1import { NextPage, GetStaticProps } from 'next';2import Link from 'next/link';3import fetch from 'node-fetch';45const TodosPage: NextPage<{6todos: { title: string; userId: string; id: string; completed: boolean }[];7}> = (props) => {8return (9<>10<h1>Todos page</h1>11<Link href="/">Home</Link>12<ul>13{props.todos.map((x) => {14return (15<li key={`/todos/${x.id}`}>16<Link as={`/todos/${x.id}`} href={`/todos/[id]`}>17{x.title}18</Link>19</li>20);21})}22</ul>23</>24);25};2627export const getStaticProps: GetStaticProps = async () => {28const todos = await fetch(29'https://jsonplaceholder.typicode.com/todos'30).then((response) => response.json());3132return {33props: { todos },34};35};3637export default TodosPage;
getStaticPaths
NextJS gives the ability for the developer to create statically generated dynamic routes, e.g. /products/:id
or /users/:name/:comment
. To have access to the path of these dynamic routes we use getStaticPaths
. Same as the function we introduced earlier, this one is an asynchronous function that returns an object with a paths
field. That paths
field contains all the paths that need to pre-rendered at build time, which means it's used for SSG.
There's also another field called fallback, I haven't used it yet, the only thing I know is that if it's set to false
and a user tries to access a path that is not returned by getStaticPaths
, it will result in a 404
.
getStaticPaths
can be used in conjunction with getStaticProps
. The paths returned are then present in the "context" of the page, which can be read by the getStaticProps
function. As an example, we could consider a static product page with a dynamic route representing a route to a given product:
Example showcasing a use case of getStaticPaths for a sample /todos/:id API
1import { NextPage, GetStaticPaths, GetStaticProps } from 'next';2import Link from 'next/link';3import fetch from 'node-fetch';45const TodoPage: NextPage<{ title: string }> = (props) => {6return (7<>8<p>{props.title}</p>9<Link href="/todos">Todos</Link>10</>11);12};1314export const getStaticPaths: GetStaticPaths = async () => {15const todos = await fetch(16'https://jsonplaceholder.typicode.com/todos'17).then((response) => response.json());1819const ids = todos.map((todo) => todo.id);20const paths = ids.map((id) => ({ params: { id: id.toString() } }));2122return {23paths,24fallback: false,25};26};2728export const getStaticProps: GetStaticProps = async ({ params: { id } }) => {29const todos = await fetch(30'https://jsonplaceholder.typicode.com/todos'31).then((response) => response.json());32const todo = todos.find((x) => x.id == id);3334return {35props: {36title: todo.title,37},38};39};4041export default TodoPage;
getServerSideProps
Sometimes static-side generation is not what we need. If we want to be able to fetch data and render dynamic content on the fly getServerSideProps
is what we need.
Like getStaticProps
, this function is asynchronous and allows us to fetch some data and returns an object with a props
field that will be passed down to a page. However, the main difference here is that getServerSideProps
allows us to pre-render a page on each request, thus we can consider that this is a use case for SSR. This means that thanks to this function, I can go fetch some non-static data that is tied to a request.
Example showcasing a use case of getServerSideProps for a sample /todos/:id API
1import { GetServerSideProps, NextPage } from 'next';2import ErrorPage from 'next/error';3import fetch from 'node-fetch';45interface Data {6id: number;7title: string;8userId: number;9completed: boolean;10}1112const Todo: NextPage<{ data: Data }> = (props) => {13if (!props.data) {14return <ErrorPage statusCode={404} />;15}1617return (18<>19<p>{props.data.id}</p>20<p>{props.data.title}</p>21<p>{props.data.userId}</p>22<p>{props.data.completed}</p>23</>24);25};2627export const getServerSideProps: GetServerSideProps = async ({28params,29res,30}) => {31try {32const { id } = params;33const result = await fetch(34`https://jsonplaceholder.typicode.com/todos/${id}`35).then((response) => response.json());3637return {38props: {39data: result,40},41};42} catch {43res.statusCode = 404;44return {45props: {},46};47}48};4950export default Todo;
What about data fetching on "non-page" components
I felt that one of the most confusing aspect of NextJS when having worked only on more classic client side rendered app, is the distinction between pages and "non-pages" components. Thankfully, I'm a heavy gatsbyJS user so I was already familiar with these concepts when I started learning about NextJS, hence, just in case I want to write a few word about it for anyone who might still be confused about pages vs components.
The functions featured above only work on pages. Which means the following:
- you can only use them in files under the
/pages
folder - they can't be used on components
- components will have to rely on client-side fetching if we want them to fetch data.
So when building your first NextJS app, you'll have to carefully architect your app and think about data beforehand and ask yourself:
- what can and should be fetched at build time?
- what can and should be server-rendered?
- which components need to have access to data and do they need to trigger some client-side fetching.
These ways of thinking about where the data comes from in these different use cases are unusual to me since I only worked on client-side apps in the past, but I feel ready to do that kind of gymnastic in my future projects as I feel these NextJS functionalities have a lot of potentials when it comes to building more efficient applications.
Liked this article? Share it with a friend on Bluesky or Twitter or support me to take on more ambitious projects to write about. Have a question, feedback or simply wish to contact me privately? Shoot me a DM and I'll do my best to get back to you.
Have a wonderful day.
– Maxime
GetServerSideProps vs GetStaticProps vs GetStaticPaths.