10.2 NextJS Serverside
# Build & Start Commands
When you are getting closer to finished with your app and you want to start seeing the production version of your site then you can run the yarn build
command. This will create the production version of your site. For NextJS, this means that there will be automatic server-side pre-rendering of your static pages.
If you look inside the /.next/server/pages/
folder you will find the pre-rendered .html
files that correspond to the pages that you build inside your /pages/
folder.
The JS code that runs on these pre-rendered pages, to make them fully interactive is a process called hydration
. The JS will make things like the <Link>
elements work. This differs from ReactJS. React JS needs JavaScript to do anything or render anything.
What NextJS is doing is called Static Site Generation
. This means that it generates some static files that will be sent to every user. Then the JS on the client side makes things interactive and load the data. These static files are created at BUILD time.
Don't confuse this with Server-Side Rendering
, which is what we do with NodeJS and Express and our APIs. With Server-Side Rendering every page is generated on every request.
The /.next/cache/images/
folder is the public location for your shared images.
The yarn start
command will start the server that provides the routing to these static pages.
# Mixed SSG and SSR
NextJS actually will let us do rendering on a per-page basis. We can select which pages are SSG
and which are SSR
.
It will be up to you as the developer, to make decisions about which is a better option for your pages and site as a whole.
If the data needs to be up to date with every request then Server-Side Rendering makes more sense.
If the data rarely changes then maybe Static Generation makes more sense.
If the content is just static HTML then definitely go with Static Generation.
# Pages with Data
# getStaticProps
If you have a page that needs to have some data fetched before displaying the content but you still want to do the static server generation of the page, then you need to use the getStaticProps
async function from NextJS.
export default function Users(props) {
//this is our page
//it will be displaying a list of users
//we want to render the list on the server at build time.
return (
<div>
props.users.map(user=>(
<p>{user.name}</p>
))
</div>
);
}
export async function getStaticProps() {
//this will be our function that will be used at
//render time to get the data and send it to `props`
//this function only runs during the pre-render
// (or with `yarn dev` on every request)
// ONLY WORKS IN A PAGE, NOT A COMPONENT
const resp = await fetch(url);
const data = await resp.json();
//we really would check for an error here
//return an object with a property called `props`
return {
props: {
users: data.results,
},
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
NextJS getStaticProps Guide (opens new window)
# getServerSideProps
If you want to follow a similar functionality as getStaticProps
but you want to do the server-side rendering version then we can use the getServerSideProps
method.
export default function Users(props) {
//this is our page
}
export async function getServerSideProps(context) {
//same as getStaticProps but the function
//runs with every request for the resource
//do fetch here...
const res = await fetch(someURL);
const data = await res.json();
//this is what you would send back if the request failed.
if (!data) {
return {
notFound: true,
};
}
return {
props: {
users: data.results,
},
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
One of the biggest differences between the two methods is the context
object passed to getServerSideProps
. It will contain the parameters for the HTTP request and response. see here for more details (opens new window)
# Client-side Rendering
If neither of those is something that you want to use, then you can use what we have done all along with ReactJS - client side rendering. Make a fetch
call from your page. Use a state variable to hold the data. Use the useEffect
hook to make the request when the page loads.
# SWR Hook
For client-side rendering there is also a React Hook called SWR
from Vercel(stale-while-revalidate) that can be used to return data from the cache first while the fetch is being made. After the fetch is successful then the cache and the page are both updated.
To use it, we first need to install it in our project.
yarn add swr
Once installed we can use it on our pages. See SWR reference (opens new window)
import useSWR from 'swr';
function Profile() {
const { data, error } = useSWR('/api/user', fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return <div>hello {data.name}!</div>;
}
2
3
4
5
6
7
8
9
# getStaticPaths
So, getStaticProps
lets you fetch a bunch of data to include in your page. This way you can build a list of cards, list items, table rows, or whatever. But, what if you wanted all those details pages (one for each item in your list) to be statically generated at build time.
You could have a hundred details pages. To statically render all those pages we need to get the original data through getStaticProps
and then create all the paths to all those pages and then generate each of them. Our static content relies on external data.
This is where getStaticPaths
becomes useful. It will return a list of all the id
values that will be used for generating all those pages. We will assume that our details page is called [id].js
and it is inside the folder /pages/notes
along side index.js
.
// /pages/notes/[id].js
//import functions to get data from a utilities file
import { getAllNoteIds, getNote } from '../../utils/notes';
//the React Component to render the page
export default function Note({ noteData }) {
return <div> ...details about a note...</div>;
}
//getStaticProps gets the data for one Note page
export async function getStaticProps({ params }) {
//needed to get the data for the current notes.
// params.id will be the id passed from the route.
// now we need to return the data into the props object.
// getNote is the imported function that
// gets the details of one note.
const noteData = getNote(params.id);
//the object with props is what gets passed to Note()
return {
props: {
noteData,
},
};
}
//getStaticPaths gets the list of possible id values
export async function getStaticPaths() {
//get the list of all the possible id values as an
//array of objects.
//const paths = getAllNoteIds()
//hard coded here for demo.
const paths = [
{ params: { id: 1 } },
{ params: { id: 3 } },
{ params: { id: 8 } },
{ params: { id: 17 } },
];
//a page will be generated for each of these ids
//remember the page is called [id].js
return {
paths,
fallback: false,
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
The fallback: false
in the returned object with the array of path ids means that ids that do not match will return the 404 page.
If fallback
is set to true
then the [id].js
page will get rendered at run time. You need to have the code in your React component to render a fallback
version. Once rendered once the static version will be cached.
Here is a sample fallback version based on the [id].js
page above.
import {useRouter} from 'next/router';
import Loader from '../../components/Loader';
export default function Note({noteData}){
const router = useRouter();
if(router.isFallback){
return <Loader/>
//to display while page being built
}
//render what you want here
return ( )
}
2
3
4
5
6
7
8
9
10
11
12
13
In dev mode, getStaticPaths
runs on every render.
In production mode, getStaticPaths
runs only once at build time.
NextJS getStaticPaths Guide (opens new window)
# API endpoints
another great feature we get for free in NextJS is the ability to create our own server-side API inside /pages/api/
folder.
NextJS uses the connect package (opens new window) which was the foundation for building Express. It was created by Sencha and Sencha Labs (opens new window) a company well known for creating great JavaScript libraries and components.
It works a lot like native NodeJS server-side code works to handle requests, with a few modifications and simplifications.
The routing works the same way as everything else in the /pages
folder. If you created /pages/api/notes/index.js
then it will be sent any HTTP requests for /api/notes
.
Warning
Do NOT make calls to your API Routes from inside of getStaticProps or getStaticPaths.
NextJS API Reference Guide (opens new window)
# A Basic Route
Let's create a basic handler for our root route - /api
.
// /pages/api/index.js
export default (req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ message: 'hello' }));
};
2
3
4
5
6
This will return a JSON file to any calls for the /api
route.
Now, what about middleware for handling things like cors
, just like we do in Express?
There are a couple packages that we can add to help us even more. next-connect
and cors
.
yarn add next-connect cors
Now we can use next-connect
as a handler for all our methods and add CORS support to every request, just like we did with Express.
// /pages/api/thing/index.js
//CRUD handler for things
import nc from 'next-connect';
import cors from 'cors';
const data = new Array(10).fill(1).map((item) => {
return {
id: Math.random()
.toString(16)
.substring(2, 10),
title: 'Some text the same as the others',
};
});
const handler = nc()
// use connect based middleware
.use(cors())
// express like routing for methods
.get((req, res) => {
//res.send('Hello world') would return text for the GET
res.json({
results: data,
});
//returns the whole data array
})
.post((req, res) => {
const id = Math.random()
.toString(16)
.substring(2, 10);
const title = req.body.title;
const obj = { id, title };
data.push(obj);
res.json(obj);
//return json for a POST request
})
.patch((req, res) => {
// PATCH /api
// req.query.id will get /api/thing/[id]
const idx = data.findIndex((item) => item.id === req.query.id);
if (idx) {
const updated = { ...obj, ...req.body };
data[idx] = updated;
req.json({ message: 'updated' });
} else {
res.status(404);
res.end();
}
})
.delete((req, res) => {
const idx = data.findIndex((item) => item.id === req.query.id);
if (!idx) {
res.status(404);
res.end();
}
data.splice(idx, 1);
res.json({ message: `Deleted ${req.query.id}` });
});
export default handler;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
This example is quite simple. It is using a generated array, not talking to a file or a database.
However, it could be talking to some other external API. NextJS provides the fetch
method to us so we can call it from our server-side functions too.