This article will walk you through using Strapi CMS with Next.js to create an application where you can register and authenticate users and persist sessions.
Let’s start with setting up the Next.js application. The easiest way to set this up is to use the create-next-app
package.
In your terminal, run npx create-next-app next-app
, where next-app
is any name you prefer for your project.
This will generate a folder named next-app
. Then, navigate into that folder in your terminal screen and run npm run dev
. You will see the application running on localhost port 3000.
Just like Next.js, Strapi also has an npm package that makes it easy to start a project.
Run npx create-strapi-app strapi-app
to create a Strapi project. When prompted, choose the Quickstart
option.
Once the installation is done, navigate to the strapi-app
folder and run npm run dev
. When you visit localhost:1337
, you will be asked to create an administrator account. After doing so, you should be directed to Strapi control panel.
In the sidebar of the Strapi under Collection Types, you should see Users
, which is created by default. This is where you can manually create users and see all of the existing users.
By default, Strapi’s API provides endpoints to create, fetch, and authenticate users.
/auth/local/register
is the end-point where we need to send the username, email, and password in order to register a user.
For this example app, we will have /register
and /profile
routes, where you can register a user and then see the profile of a user, respectively. The login screen will be on the homepage.
Let’s start with the <RegisterComponent/>
component.
This component returns a very basic form, where the user enters their username, email, and password.
Then userData
object holds this information and sends it to the /api/register
endpoint, which is something we will create within the backend part of the Next application. Note that to make the API call, I use the axios
package, so make sure you installed it as a dependency.
npm install --save axios
Once the request made to /api/register
is successful, we route the application to /profile
in order to display the user information. We will create the /profile
page later.
The following file is /components/registerComponent.jsx
, and we will use it in /pages/register.jsx
:
import { useState } from 'react'; import { useRouter } from 'next/router'; import axios from 'axios'; const RegisterComponent = () => { const router = useRouter(); const [userData, setUserData] = useState({ username: '', email: '', password: '', }) const handleSubmit = async (e) => { e.preventDefault(); try { await axios.post('/api/register', userData); router.replace('/profile'); } catch (err) { console.log(err.response.data); } } const handleChange = (e) => { const { name, value } = e.target; setUserData({...userData, [name]: value }); } return ( <form onSubmit={handleSubmit}> <label> Username: <input type="text" name="username" onChange={e => handleChange(e)} /> </label> <br /> <label> Email: <input type="text" name="email" onChange={e => handleChange(e)} /> </label> <br /> <label> Password: <input type="password" name="password" onChange={e => handleChange(e)} /> </label> <br /> <button>Register</button> </form> ) } export default RegisterComponent;
Now create register.jsx
under the pages
folder and just return the <RegisterComponent/>
.
import RegisterComponent from "../components/registerComponent"; const Register = () => ( <RegisterComponent /> ) export default Register;
The next thing we need to do is create the /api/register
endpoint.
Under /pages
, you will find the /api
folder. In that folder, create register.js
. That way, when we make a request to /api/register
in the Next.js client, it will be handled by this file.
import axios from 'axios'; import { setCookie } from 'nookies' export default async (req, res) => { const { username, password, email } = req.body; try { const response = await axios.post('http://localhost:1337/auth/local/register', { username, email, password, }) setCookie({ res }, 'jwt', response.data.jwt, { httpOnly: true, secure: process.env.NODE_ENV !== 'development', maxAge: 30 * 24 * 60 * 60, path: '/', }); res.status(200).end(); } catch (e) { res.status(400).send(e.response.data.message[0].messages[0]); } }
As mentioned before, the endpoint for registering a user is /auth/local/register
. Because Strapi is running on localhost:1337
, we send a request to http://localhost:1337/auth/local/register
, along with the username, email, and password data that we retrieved from the request body.
Note that if you deploy your Strapi application to a remote server, then you need to replace the base URL accordingly. It’s good practice to store the base URL in an environment variable and then use it instead of hardcoding the URL. However, for the sake of simplicity, I will just use localhost:1337
throughout this article.
The response returned from the POST request contains a JWT token. This token is specific to that user and should be stored securely in order to automatically authenticate the user.
The nookies
package is a collection of helper functions that handle cookies in a Next.js application.
The setCookie
function takes the response object as the first argument. The second argument is the name of the cookie, and this can be anything you choose.
The third argument is the value of the cookie. In our case, this is the JWT
token returned from the request response, and, as the last argument, we can pass an object with options.
The httpOnly
flag prevents the client — the javascript running on the browser — from accessing the cookie so that you can protect the cookie from possible cross-site scripting (XSS) attacks.
The secure
flag ensures that the cookie is only transmitted over a secure https
connection. Because the localhost is not an https
connection, we set it to false
if the environment we are running the application is development
.
maxAge
determines how many seconds the cookie will be valid. In this example, it is set to 30 days.
path
determines in which path the cookie should be valid. It is set to /
in order to make the cookie available for all the paths.
Note that we are sending a message in case of a failed request. This message retrieved from e.response.data.message[0].messages[0]
contains useful information regarding why the request failed. This might be an invalid email, or a username already in use, etc.
So, this information can be used to show the appropriate error message in case of a failed registration.
For the sake of simplicity, we don’t handle this error message in the client. We simply log it to show in the browser console instead.
This is the /profile
route, where the user data, such as username and email, is displayed. This route can be directly visited, or, the user could be routed there when logged in or signed up.
Because this page needs to use the cookie in order to authenticate the user, and because we have set the cookie on the server side with the httpsOnly
flag, we need to read the cookie in the server as well.
getServerSideProps
is a Next.js function that runs on the server on each request. In this function, we can parse the cookie in order to access the JWT token and then make a request to Strapi to fetch the user data. Then we can return that data from the function, which exposes it as props to the page.
Content of /pages/profile.jsx
is as follows:
import { useRouter } from 'next/router'; import axios from 'axios'; import nookies from 'nookies'; const Profile = (props) => { const router = useRouter(); const { user: { email, username } } = props; const logout = async () => { try { await axios.get('/api/logout'); router.push('/'); } catch (e) { console.log(e); } } return ( <div> <div>Username: {username}</div> <div>Email: {email}</div> <button onClick={logout}>Logout</button> </div> ) } export const getServerSideProps = async (ctx) => { const cookies = nookies.get(ctx) let user = null; if (cookies?.jwt) { try { const { data } = await axios.get('http://localhost:1337/users/me', { headers: { Authorization: `Bearer ${cookies.jwt}`, }, }); user = data; } catch (e) { console.log(e); } } if (!user) { return { redirect: { permanent: false, destination: '/' } } } return { props: { user } } } export default Profile;
Let’s focus on the getServerSideProps
function first. We again use the nookies
package to get the cookies from the context object (ctx). The name of the cookie we set was jwt
, hence the check for existence of cookies.jwt
.
If this specific cookie is there, then we send a request to the /users/me
endpoint of the local Strapi server with the Authorization
header containing the JWT token to fetch the user information.
If the cookie does not exist or the JWT token is invalid, the user
variable will stay null
and we redirect the page back to /
, which is the home page with the login screen. Otherwise, we return user
in the props
object, which then becomes available as a prop in the Profile
function we export.
Profile
function returns a very basic markup where the username and email are displayed, along with a log-out button, which calls the logout
function.
This function sends a request to the /api/logout
endpoint in order to delete the cookie and then route the page to /
.
Below is /pages/api/logout.js
:
import { destroyCookie } from 'nookies' export default async (req, res) => { destroyCookie({ res }, 'jwt', { path: '/', }); res.status(200).end(); }
Here’s the content of /pages/index.jsx
, which is the homepage and where the <LoginComponent>
lies along with a Register button, which routes the page to /register
route.
import { useRouter } from 'next/router'; import axios from 'axios'; import nookies from 'nookies'; import LoginComponent from '../components/loginComponent'; const Home = () => { const router = useRouter(); const goToRegister = () => { router.push('/register'); } return ( <div> <LoginComponent /> <button onClick={goToRegister}>Register</button> </div> ) } export const getServerSideProps = async (ctx) => { const cookies = nookies.get(ctx) let user = null; if (cookies?.jwt) { try { const { data } = await axios.get('http://localhost:1337/users/me', { headers: { Authorization: `Bearer ${cookies.jwt}`, }, }); user = data; } catch (e) { console.log(e); } } if (user) { return { redirect: { permanent: false, destination: '/profile' } } } return { props: {} } } export default Home;
As we did in /pages/profile.jsx
, we again use the getServerSideProps
function to fetch the cookie, and then check the existence of the user.
If the user does exist, then we redirect to /profile
route. If not, we return an empty props
object and stay on the same route to render the <LoginComponent/>
, which has the following content:
import { useState } from 'react'; import { useRouter } from 'next/router'; import axios from 'axios'; const LoginComponent = () => { const router = useRouter(); const [userData, setUserData] = useState({ identifier: '', password: '', }); const handleSubmit = async (e) => { e.preventDefault(); try { await axios.post('/api/login', { ...userData }); router.push('/profile'); } catch (err) { console.log(err.response.data); } } const handleChange = (e) => { const { name, value } = e.target; setUserData({...userData, [name]: value }) } return ( <div> <form onSubmit={handleSubmit}> <label> Email: <input type="text" name="identifier" onChange={e => handleChange(e)} /> </label> <br /> <label> Password: <input type="password" name="password" onChange={e => handleChange(e)} /> </label> <br /> <button>Login</button> </form> </div> ) } export default LoginComponent;
This component renders a form to get the email and password. When the form is submitted, a POST request to /api/login
is made and the page is redirected to /profile
route.
Below you can see the content of /api/login.js
import axios from 'axios'; import { setCookie } from 'nookies' export default async (req, res) => { const { password, identifier } = req.body; try { const postRes = await axios.post('http://localhost:1337/auth/local', { identifier, password, }) setCookie({ res }, 'jwt', postRes.data.jwt, { httpOnly: true, secure: process.env.NODE_ENV !== 'development', maxAge: 30 * 24 * 60 * 60, path: '/', }); res.status(200).end(); } catch (e) { res.status(400).send(e.response.data.message[0].messages[0]); } }
/auth/local
is a Strapi endpoint to log a user in. The object sent to this endpoint should have the identifier
and password
keys.
As we did in /api/register.js
, we also set a cookie with the same name and options to persist the user session.
In this article, we demonstrated how to use Next.js as a full-stack application to create a user interface with multiple routes and create API endpoints to communicate with Strapi’s API in order to register and fetch user information. Setting and fetching cookies on the server-side within Next.js is also demonstrated in order to persist user sessions.
Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your Next.js apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]