5 Tips for Building a Next.js App
I recently had a pleasure of using Next.js, the server side React framework, for building a small product — a free global tech job board. You can see it live at https://spaceboard.tech/.
I generally approach Server Side Rendering (SSR) with caution, because of it’s performance and complexity implications. However, SSR made sense for this application and Next.js really excels at developer experience.
Here are 5 tips for building a Next.js app.
1. Using Dynamic Routes
Next makes it easy to get started by treating all modules in pages
directory as distinct routes. E.g. visiting /about
renders the React component in pages/about.js
.
In most applications you need some dynamic parameters to render your page. For example, in the case of Spaceboard, the list of jobs could get long and is therefore paginated. To specify which page should be rendered, we could use a query parameter ?page=5
. Query parameters get passed to component’s getInitialProps
as query
and can be used to fetch the appropriate data.
It is possible to use parametrised URLs in Next instead, e.g. /page/2
instead of /?page=2
. Here’s the first tip: you’ll have much easier time if you stick to query params only and resist the temptation to use parametrised URLs. That is because if you use parametrised URLs, Next can no longer generate correct links in your application for you.
If you’re already planning to use a custom server implementation for other reasons, such as caching, or if you, like me, think that parametrised URLs just look so much cooler — read on.
I won’t go into the detail about how to setup a server for custom routing since Next.js has documentation and examples for that. But I’ll show you how I approached generating links throughout the application.
Since the parametrised route patterns are defined in a custom server outside of Next’s boundaries, Next doesn’t know anything about how to generate such links. So if you use the Link
component it might navigate users to the wrong URL. For example, if you want to create a link that extends existing query parameters to add a filter, you’d expect it to work like this:
/page/2 -> /page/2?location=london
But, the Link
component would link you like this:
/page/2 -> /?page=2&location=london
To solve this you can create a function that takes the current route, and the next desired query and pathname and returns a { href, as }
tuple. Here href
is an internal Next link represtantation { pathname, query }
and as
is the link we want to use in the browser’s address bar. You would then use this link generating function like so:
Here is a gist of the implementation of the link
function that I used.
If you make use of the new React hooks feature, this gets even nicer since you can access router via context in your hook:
Finally, the reason I chose not to create a wrapper Link
component and used a function that returns { href, as }
is because sometimes you need to navigate imperatively without using the Link
component.
2. React Hooks
In the previous section I‘ve mentioned React hooks. Hooks is a new, experimental feature shipped in React v16.7.0-alpha. Be minfdul when using this feature as the API could change in the next stable version of React.
Having said that, hooks work great together with Next.js. I used them for:
- accessing the current route information instead of having to wrap components using
withRouter
- reading and writing cookies and abstracting away the client and server differences in the cookie API
- accessing state from a central store and triggering actions
- generating links as we saw in the previous section
- loading components asynchronously to speed up initial bundle size
Here’s a taster for how using hooks looks like:
To get more ideas for how hooks could be useful in your applications check out https://usehooks.com/.
3. Central Store
You can do a lot in Next.js by leveraging the getInitialProps
API and local component state. However, I always find the central store approach leads to a cleaner and more robust application implementation.
For example, in Spaceboard, I had to reflect the logged in user state in multiple places:
- display Login vs Logout link dependong on whether user is logged in
- prepopulate the Post form with the job details of the last job user posted
- allow user to Logout in the form, inline, without losing form state
- display admin controls in case the user has admin role
My go to solution for situations like this is to centralise all application state into a redux like store. I never got fully behind the Redux API and thefore always use an alternative, but the principle is the same.
To use a central store in Next requires a bit of juggling. There’s a good example in Next examples collection, but in principle you need to
- Create the store on the server
- Preload it with all the data required to render the page in
getInitialProps
- Then recreate the store on the client
- Initialise it with the data serialised by Next and passed to the
pages/_app.js
component via props - Phew 😅
Here’s how a stripped down pages/_app.js
looked like in my app:
With this setup, we can now:
- access and render any bit of state in any of the app’s components with a single line of code, e.g.
useAtom(state => state.user.loggedIn)
- avoid having to use
withRouter
to read the current route information - employ
useLink
anduseCookie
hooks that access route or cookie in the state - trigger actions to transition our state, e.g.
deleteJob
,countClick
,storeFilter
,navigate
,logout
There is one caveat when using this approach that I avoided to mention so far. And that is data fetching race conditions. If user clicks several links in rapid succession without waiting for the page to load, it’s important to either cancel the previous requests or at least ignore them to avoid rendering the wrong content. Normally Next.js handles this for us by only using the results of the lastgetInitialProps
call. But by opting into using a central store, this becomes our responsibility. In the interest of time I won’t go into detail about how to handle this, but here’s one possible solution.
4. Server Side API
In my experience, it’s common to develop an API together with developing a UI. This was also the case for Spaceboard. Next.js already has a server component, this is the part of Next that renders your React pages into html and serves them up to the browser. How could we extend this server to also contain API endpoints of our application? And should we even be doing that?
What I find to be most effective for getting the best development experience and simplifying the deployment is to split Next and API into separate entry points for development but combine them into a single process for production. This is fairly straightforward when using the Fastify framework, but also possible with other server frameworks.
We create 3 entry points to our application, 2 for development and 1 for production. Here’s ./bin/next
entry point that starts the Next server:
This is ./bin/api
that starts the API server:
Now, you can run ./bin/next
in one tab and nodemon ./bin/api
in another tab of your terminal to get both the Next server and API server started and watching for changes to the code.
In production, we mount both next
and api
subapplications in the same process in the production entry point ./bin/www
:
You can deploy this application to a server, for example, using Dokku and use ./bin/www
as the entry point and have both Next.js logic and your API logic run in the single process, which simplifies things like monitoring.
One exercise left for the reader is the implementation of the next.js plugin used in the above snippets, but as usual, Next.js has a good example. For proxying the api requests in development I’ve used the k-fastify-gateway plugin.
5. Caching
If you have pages that don’t change frequently and want to be able to absorb traffic spikes, you have an option to wrap Next.js SSR rendering in a little bit of caching. To be fair, this shouldn’t be the first thing you do, Next.js is performant enough out of the box for your typical application. But it’s good to know this option exists, because rendering an entire page on the server using React isn’t the lightest of operations. As usual, Next has an example for how to achieve this.
—
Thanks for reading! I hope this post taught you something new you could use in your future development or gave you inspiration to try new workflows and tools. And please share https://spaceboard.tech/ to anyone you know is looking for a job or looking for people, every little bit helps.