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
Leave A Comment