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,
    },
  };
}
1
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,
    },
  };
}
1
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
1

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>;
}
1
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,
  };
}
1
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 ( )
}
1
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' }));
};
1
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
1

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;
1
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.

Last Updated: : 11/9/2021, 10:47:39 AM