The Ultimate Guide to Next.js Authentication
- Category
- Guides
- Published
In this guide, you will learn best practices for implementing secure authentication in your Next.js app.
Next.js has become the go-to framework for JavaScript and React development. It has become so popular because of the superior developer experience, performance optimization features, and its ability to facilitate different rendering options and serverless functions with ease. The combination of these attributes makes it an appealing choice for both new and experienced developers.
In Next.js 13, the Next.js team significantly upgraded the capabilities of the framework. Moving to what they call the “App Router,” Next.js is now based on React Server Components which boast performance upgrades over the previous client-server architecture.
But this shift has also led to a change in how authentication works in Next.js. In this article we want to provide an up-to-date guide on how authentication now works on Next.js so you can easily understand the concepts involved and easily add secure authentication to your Next.js applications.
The Next.js App Router
The App Router paradigm was introduced in Next.js 13 and is a significant shift in how the Next.js framework works.
Next.js was initially built as a React framework to facilitate server-side rendered and statically generated sites. But as the framework grew, more options became available:
- Client-Side Rendering (CSR) is where content is rendered on the client side, after the initial page load. This is ideal for pages that don’t require SEO or initial content served by the server. Common for user dashboards or pages behind authentication. You use React state and effects (
useState
,useEffect
, etc.) as you would in a regular React app. - Static Site Generation (SSG) is where pages are pre-rendered at build time and served statically. Each request receives the same HTML. This is good for content that doesn’t change often and doesn’t need to be updated with every request, such as blogs, marketing pages, and documentation, etc. Here, you use
getStaticProps
to fetch the required data at build time. - Incremental Static Regeneration (ISR) lets you statically generate pages with the option to update them in the background. Once updated, subsequent requests get the new version. This is useful for content that updates periodically but doesn’t need real-time updates. Like with SSG, you can use
getStaticProps
, but this time you add arevalidate
property. Therevalidate
interval (in seconds) determines how often the page should be updated. - Server-Side Rendering (SSR) means each request is rendered on-the-fly on the server, providing fresh data. It is ideal for pages that need real-time data or when content changes frequently. Also beneficial for SEO. You use
getServerSideProps
to fetch data on each request.
You could have some pages work with CSR, some SSG, and some SSR. But this also led to confusion and performance issues with sites not optimally built.
Next.js 13 and the App Router are designed to simplify the developer experience within Next.js, and rebuild the framework around React Server Components. With RSC, the default for the entire site is server-side rendering, with each page being opted-in to CSR as needed. This leads to performance gains, as rendering on the server is faster, and you don’t have to send heavy JavaScript payloads over the network for hydration. It also means more sites can work on the ‘edge’ network, where sites are served from multiple servers close to users.
But this does mean that the way developers have been using Next.js thus far has now changed. The old, Pages Router continues to work, and can be used side-by-side with the App Router, but significant changes in how data is served, such as no request/response objects being passed means developers will need to rethink how they work, and how their authentication works.
Here, we’re going to go through:
- How authentication works with the pages router in Next.js
- How authentication works with the App Router in Next.js
This will give you the ability to use either as you need, and to see the differences in each paradigm when it comes to authentication.
Authentication With the Next.js Pages Router
Before we get into authentication with the App Router, let’s look at how authentication works with the Pages Router in Next.js.
Let’s create a Next.js project. To do this, we use:
You will be prompted to choose different options, but given that we are explicitly using the Pages Router, you’ll want to select “no” when asked if you would like to use the App Router.
With that done, we can install Clerk:
Next, we want to add our API keys as environmental variables in an .env.local
file. To get these, sign up for a free Clerk account:
Before we go any further, we’ll also make some small changes to our homepage, at pages/index.js
:
If we now run npm run dev
, we’ll get this homepage:
We’re now ready to start adding Clerk to our code. The first thing we need to do is mount the ClerkProvider. The ClerkProvider
is the core component of Clerk. It is what handles all the active session data and the user context. Whenever a Clerk hook (or any other components) needs authentication data, it is getting it from the ClerkProvider
.
The ClerkProvider
needs needs to access headers to authenticate any user. This is important because:
To protect your entire application, it is recommended to wrap our main Component in the _app.tsx|jsx
file:
This will protect every page within the application and will make the user context accessible anywhere within the app, but will also opt every page into dynamic rendering.
If you have statically served pages or are using incremental static regeneration, you can wrap route groups further into your application. For example, you might want to leave your marketing site unprotected, but wrap your dashboard components in the ClerkProvider
.
Clerk requires middleware to determine the routes that need to be protected. This is in middleware.js
in our root directory. This matcher will put every page and API route on the site behind authentication:
The entire application is now protected. Accessing any page while signed out will redirect you to the sign up page. If we go to the homepage again, we get redirected:
Usually, this isn’t what you want. Your homepage, product, and marketing pages aren’t very helpful if they are behind authentication!
If you navigate to the homepage now you’ll also get this warning:
To prevent this behavior, choose one of:
- To make the route accessible to both signed in and signed out users, pass
publicRoutes: ["/"]
toauthMiddleware
- To prevent Clerk authentication from running at all, pass
ignoredRoutes: ["/((?!api|trpc))(_next.*|.+\\.[\\w]+$)", "/"]
toauthMiddleware
- Pass a custom afterAuth to
authMiddleware
, and replace Clerk’s default behavior of redirecting unless a route is included in public routes
So let’s make a change to add a public route:
Now, when we head to the homepage, it is available again:
Great. But now there is no way for us to sign up or sign in. Clerk’s Next.js Authentication SDK provides helpers for determining whether a user is signed in or note, and conditionally changing the text. Let’s wrap our text in these components:
Here, if a user is signed out, they’ll see a link to a sign up page. This page doesn’t exist yet, so lets create it. It will live at pages/sign-up/[[...index]].jsx
:
Not much to it. If you try to go that page, you’ll be redirected to sign in/sign up, so it will look like its working. But you are actually being redirected because that page hasn’t been defined as a public route in the middleware, so it itself is behind authentication. To get around this, we’re actually going to add it to our environment variables as a special page:
Now, if we go to that sign up page, we’ll get the modal where we can sign up:
Then, as our environment variables say, we’re redirected to the home page:
As you can see, the link to sign in as gone, as it was wrapped in the component that only shows it if you are signed out.
We now have a problem. We are now signed in (great!), but we can’t sign out (uh oh!). We could create a specific link for this as well, but Clerk provides a UserButton component that allows us to do this easily. Let’s add that to the homepage:
We’ve wrapped this UserButton
within the SignedIn component so it’ll only show when the user is signed in:
Using that button, the user can easily adjust their profile as well as sign out of the application.
Now the user is signed in, they can visit other pages. Let’s create a pages/protected.jsx
page:
The code uses the clerkClient
, getAuth
, and buildClerkProps
utilities from the @clerk/nextjs
library to handle user authentication. If a user is not authenticated (determined by the absence of a user
prop), the page prompts the user to log in. If authenticated, the page displays a personalized welcome message.
The server-side function getServerSideProps
checks for the userId
in the incoming request using the getAuth
function; if the userId
is present (indicating authentication), it fetches detailed user information from Clerk via clerkClient.users.getUser
. The fetched data is then streamlined to a simpler format, extracting only necessary fields like the user’s first name and last name.
The getServerSideProps
function concludes by returning the user data, if any, along with additional props sourced from buildClerkProps
, ensuring the frontend receives the necessary data to render the page appropriately.
This renders as:
Here, the authentication is checked on the server before the page is rendered via getServerSideProps
. We can also check whether the user is authenticated on the client. Let’s create a pages/protected-client.jsx
page:
Which renders this page:
One final part of Next.js we can protect with authentication–API routes. With the pages router, any route within pages/api is returned as an API. Let’s protect an API route at pages/api/auth.js
:
This uses the getAuth
helper that retrieves the authentication state to protect the API route. If we call this within the browser, we’ll get the user JSON:
You now have a protected Next.js application using the pages router. Let’s move on to the App Router.
Authentication with the Next.js App Router
We’re now going to work through an entire example with the Next.js App Router. Some of this is the same as above, particularly, the set up, but once we get into the actual authentication, there are going to be some differences.
Let’s create a new Next.js project:
This time, when you are prompted to use the App Router, select “yes”.
Then install Clerk into this project:
And then add our API keys in an .env.local
file:
We’ll spool up another bare-bones homepage, this time at app/page.js
:
If we now run npm run dev
, we’ll get this homepage:
Looks like we have some nicer default styling with the App Router.
Next up is to add the ClerkProvider
. This is the first point at which there is a difference between the pages router and the App Router. With the App Router, we’re going to add the ClerkProvider
to our layout.js
:
The App Router doesn’t use the _app.js
file. Instead, layout.js lets you set a global layout for your application. Therefore you can add the ClerkProvider
to e.g. the dashboard/layout.js
file, and only the files within that route group would require a login. To repeat from above, this is important because the ClerkProvider
requires dynamic rendering. If you nest the ClerkProvider
at a lower level, this allows you to serve the landing and marketing pages statically.
Next step is the middleware. Again, this is in middleware.js
in our root directory. This time, we’ll start with our homepage as a public route:
Clerk is now protecting the entire application. Let’s change our homepage to use the SignedOut component so we can sign in to the app:
The link now shows on the page:
Next we’ll add the two other components that will be important for signing up/in/out. First, the sign up page, which will be at app/sign-up/[[...sign-up]]/page.jsx
:
Remember to also add these new endpoints to your .env.local
:
Second, we need the user button:
Now, when we click the sign up/in link we again go through to the Clerk modal:
Once we sign in we are redirected back to the homepage, now with the user button:
With the user signed in, let’s do the same as with the pages router: create a protected page and a protected API.
Again, for protected pages, we can check for authentication on the server or on the client. Let’s quickly do the client side as that is similar to the pages router:
The only difference between the App Router and the pages router version here is the “use client” directive. By default, all components in the App Router are server components, so will render on the server. The “use client” directive opts this component into client-side rendering, so we can then use Clerk hooks.
This renders as:
For server rendering, Clerk has two App Router-specific helpers that you can use with your Server Components:
- auth() will return the Authentication object of the currently active user. One of the fundamental differences with the App Router is that you don’t have to inspect the request object constantly for authentication headers. Instead they are available in the global scope through Next.js’s headers() and cookies() functions, that ClerkProvider gives you access to.
- currentUser() will return the User object of the currently active user so you can render information, like their first and last name, directly from the server.
Here, we’re going to use both to get information about the user on a protected page:
Which gives us a page with user information rendered:
For API routes, the routing is different, but the code is similar. Let’s build an API route under app/api/user/route.jsx
:
We don’t have the request and response objects with the App Router, so get all our authentication data from our Clerk helpers (which get it from the ClerkProvider which gets it from the global scope via the Next.js headers() function). We can return a JSON of the user’s information:
And that’s it. You have server-side, client-side, and API route authentication using the Next.js App Router.
Authenticating with the App Router
The Next.js App Router offers significant performance gains compared to the traditional pages router. However, you can absolutely continue leveraging the pages router in your Next.js applications – Clerk supports authentication with both routing approaches.
To learn more about the App Router, check out the official Next.js documentation. If you would like to get started with Clerk, sign up for a free account and follow along with our Next.js Quickstart.
Ready to get started?
Sign up today