This series is my Next.js study resume, and despite it’s keen to a vanilla Next.js,  all the features are applicable with Sitecore SDK. It is similar to the guide I recently wrote about GraphQL and aims to reduce the learning curve for those switching to it from other tech stacks.

In part 1 we covered some fundamentals of Next.js – rendering strategies along with the nuances of getStaticProps, getStaticPaths, getServerSideProps as well as data fetching.
In part 2 we spoke about UI-related things coming OOB with Next.js – layouts, styles and fonts powerful features, Image and Script components, and of course – TypeScript.
In part 3 we went through the nuances of Next.js routing and explained middleware

In this post we are going to talk about pre-going live optimizations sich as caching and reducing bundle size as well as authentication.

Going live consideration

use caching wherever possible (see below)
make sure that the server and database are located (deployed) in the same region
minimize the amount of JavaScript code
delay loading heavy JS until you actually use it
make sure logging is configured correctly
make sure error handling is correct
configure 500 (server error) and 404 (page not found) pages
make sure the application meets the best performance criteria
run Lighthouse to test performance, best practices, accessibility, and SEO. Use an incognito mode to ensure the results aren’t distorted
make sure that the features used in your application are supported by modern browsers
improve performance by using the following:

next/image and automatic image optimization
automatic font optimization
script optimization

Caching

Caching reduces response time and the number of requests to external services. Next.js automatically adds caching headers to statics from _next/static, including JS, CSS, images, and other media.

Cache-Control: public, max-age=31536000, immutable

To revalidate the cache of a page that was previously rendered into static markup, use the revalidate setting in the getStaticProps function.

Please note: running the application in development mode using next dev disables caching:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate

Caching headers can also be used in getServerSideProps and the routing interface for dynamic responses. An example of using stale-while-revalidate:

// The value is considered fresh and actual for 10 seconds (s-maxage=10).
// If the request is repeated within 10 seconds, the previous cached value
// considered fresh. If the request is repeated within 59 seconds,
// cached value is considered stale, but is still used for rendering
// (stale-while-revalidate=59)
// The request is then executed in the background and the cache is filled with fresh data.
// After updating the page will display the new value
export async function getServerSideProps({ req, res }) {
res.setHeader(
‘Cache-Control’,
‘public, s-maxage=10, stale-while-revalidate=59’
)

return {
props: {}
}
}

Reducing the JavaScript bundle volume/size

To identify what’s included in each JS bundle, you can use the following tools:

Import Cost – extension for VSCode showing the size of the imported package
Package Phobia is a service for determining the “cost” of adding a new development dependency to a project (dev dependency)
Bundle Phobia – a service for determining how much adding a dependency will increase the size of the build
Webpack Bundle Analyzer – Webpack plugin for visualizing the bundle in the form of an interactive, scalable tree structure

Each file in the pages directory is allocated into a separate assembly during the next build command. You can use dynamic import to lazily load components and libraries.

Authentication

Authentication is the process of identifying who a user is, while authorization is the process of determining his permissions (or “authority” in other words), i.e. what the user has access to. Next.js supports several authentication patterns.

Authentication Patterns

Each authentication pattern determines the strategy for obtaining data. Next, you need to select an authentication provider that supports the selected strategy. There are two main authentication patterns:

using static generation to load state on the server and retrieve user data on the client side
receiving user data from the server to avoid “flushing” unauthenticated content (in the meaning of switching application states being visible to a user)

Authentication when using static generation

Next.js automatically detects that a page is static if the page does not have blocking methods to retrieve data, such as getServerSideProps. In this case, the page renders the initial state received from the server and then requests the user’s data on the client side.

One of the advantages of using this pattern is the ability to deliver pages from a global CDN and preload them using next/link. This results in a reduced Time to Interactive (TTI).

Let’s look at an example of a user profile page. On this page, the template (skeleton) is first rendered, and after executing a request to obtain user data, this data is displayed:

// pages/profile.js
import useUser from ‘../lib/useUser’
import Layout from ‘./components/Layout’

export default function Profile() {
// get user data on the client side
const { user } = useUser({ redirectTo: ‘/login’ })

// loading status received from the server
if (!user || user.isLoggedIn === false) {
return <Layout>Loading…</Layout>
}

// after the request is completed, user data is
return (
<Layout>
<h1>Your profile</h1>
<pre>{JSON.stringify(user, null, 2)}</pre>
</Layout>
)
}

Server-side rendering authentication

If a page has an asynchronous getServerSideProps function, Next.js will render that page on every request using the data from that function.

export async function getServerSideProps(context) {
return {
props: {} // will get passed down to a component as props
}
}

Let’s rewrite the above example. If there is a session, the Profile component will receive the user prop. Note the absence of a template:

// pages/profile.js
import withSession from ‘../lib/session’
import Layout from ‘../components/Layout’

export const getServerSideProps = withSession(async (req, res) => {
const user = req.session.get(‘user’)

if (!user) {
return {
redirect: {
destination: ‘/login’,
permanent: false
}
}
}

return {
props: {
user
}
}
})

export default function Profile({ user }) {
// display user data, no loading state required
return (
<Layout>
<h1>Your profile</h1>
<pre>{JSON.stringify(user, null, 2)}</pre>
</Layout>
)
}

The advantage of this approach is to prevent the display of unauthenticated content before performing a redirect. Тote that requesting user data in getServerSideProps blocks rendering until the request is resolved. Therefore, to avoid creating bottlenecks and increasing Time to First Byte (TTFB), you should ensure that the authentication service is performing well.

Authentication Providers

If you have a database of users, consider using one of the following solutions:

next-iron-session – low-level encoded stateless session
next-auth is a full-fledged authentication system with built-in providers (Google, Facebook, GitHub, and similar), JWT, JWE, email/password, magic links, etc.

If you prefer using passport:

with-passport
with-passport-and-next-connect