Implementing Server Actions and Stateless Authentication in Next.js
Leapcell: The Best of Serverless Web Hosting Detailed Explanation of Server Actions and Stateless Sessions in Next.js Introduction With the release of the widely popular App Router, Next.js has introduced an important new feature: Server Actions. Server Actions are designed to assist with server-side data operations. By reducing the dependence on client-side JavaScript, they gradually enhance form functionality. With this feature, developers can create powerful web applications using JavaScript and React without relying on traditional REST APIs. This article will delve into the advantages of Server Actions and demonstrate their practical application by implementing stateless sessions based on cookies. At the same time, this article will serve as a step-by-step guide, providing a detailed introduction to the entire process of building a demonstration project using the App Router. Getting Rid of REST APIs In the traditional development model, when creating a Next.js web application that queries a database on the backend, it is usually necessary to create REST APIs to verify the identity status and query the database. The React application on the frontend is responsible for calling these APIs. However, when the React application is the only client and there is no need to open the APIs to the public, using REST APIs can be redundant because these APIs will only be called by the application itself. With Server Actions, React components can directly run server-side code. Developers do not need to manually create API endpoints, and Next.js will automatically create corresponding endpoints for Server Actions in the background. When a Server Action is called, Next.js will send a POST request containing operation metadata to the current page to execute the corresponding operation. The Need for Stateless Sessions Next.js, as the preferred framework for creating stateless applications, often runs in a serverless environment, which means that in-memory storage of session data is not possible. Traditional session management usually relies on external storage services, such as Redis or a database. However, when the amount of session data is small, an alternative solution is to design stateless sessions through secure encryption methods and cookies stored on the client side. This approach does not require external storage, decentralizes session data, and has significant advantages in reducing server load and improving the overall performance of the application. Therefore, our goal is to build a lightweight and efficient stateless session system that fully utilizes the client-side storage capabilities and ensures data security through rigorous encryption measures. Basic Session Implementation First, you need to start a new project: npx create-next-app@latest For more installation details, you can refer to the official guide. Building the Session Library To make it easier to understand Server Actions, we will first create a simplified session system. This version will use JSON to store session data in cookies. Create the session/index.ts file and add the following code: "use server"; import { cookies } from 'next/headers'; export type Session = { username: string; }; export const getSession = async (): Promise => { const cookieStore = cookies(); const session = cookieStore.get('session'); if (session?.value) { return JSON.parse(session.value) as Session; } return null; }; export const setSession = async (session: Session) => { const cookieStore = cookies(); cookieStore.set('session', JSON.stringify(session)); }; export const removeSession = async () => { const cookieStore = cookies(); cookieStore.delete('session'); }; The first line of the code "use server" marks the functions in this file as Server Actions. Since direct access to request and response is not possible, next/headers is used here to read and write cookies, and this functionality is only available in Server Actions. Implementing Two New Server Actions After having the session library, the next step is to implement the login and logout functions to demonstrate the usability of the session system. Add the following code to the user/index.ts file: "use server"; import { removeSession, setSession } from '@/session'; export const signIn = async (username: string) => { await setSession({ username }); }; export const signOut = async () => { await removeSession(); }; Here, a simplified login process is adopted, and logging in only requires entering the username. Building the Page In the App Router, pages are usually asynchronous components, and Server Actions cannot be directly called from such components. You need to create components using "use client": components/sign-in.tsx "use client"; import { signIn } from '@/user'; import { useState } from'react'; const SignIn = () => { const [username, se

Leapcell: The Best of Serverless Web Hosting
Detailed Explanation of Server Actions and Stateless Sessions in Next.js
Introduction
With the release of the widely popular App Router, Next.js has introduced an important new feature: Server Actions. Server Actions are designed to assist with server-side data operations. By reducing the dependence on client-side JavaScript, they gradually enhance form functionality. With this feature, developers can create powerful web applications using JavaScript and React without relying on traditional REST APIs.
This article will delve into the advantages of Server Actions and demonstrate their practical application by implementing stateless sessions based on cookies. At the same time, this article will serve as a step-by-step guide, providing a detailed introduction to the entire process of building a demonstration project using the App Router.
Getting Rid of REST APIs
In the traditional development model, when creating a Next.js web application that queries a database on the backend, it is usually necessary to create REST APIs to verify the identity status and query the database. The React application on the frontend is responsible for calling these APIs. However, when the React application is the only client and there is no need to open the APIs to the public, using REST APIs can be redundant because these APIs will only be called by the application itself.
With Server Actions, React components can directly run server-side code. Developers do not need to manually create API endpoints, and Next.js will automatically create corresponding endpoints for Server Actions in the background. When a Server Action is called, Next.js will send a POST request containing operation metadata to the current page to execute the corresponding operation.
The Need for Stateless Sessions
Next.js, as the preferred framework for creating stateless applications, often runs in a serverless environment, which means that in-memory storage of session data is not possible. Traditional session management usually relies on external storage services, such as Redis or a database.
However, when the amount of session data is small, an alternative solution is to design stateless sessions through secure encryption methods and cookies stored on the client side. This approach does not require external storage, decentralizes session data, and has significant advantages in reducing server load and improving the overall performance of the application. Therefore, our goal is to build a lightweight and efficient stateless session system that fully utilizes the client-side storage capabilities and ensures data security through rigorous encryption measures.
Basic Session Implementation
First, you need to start a new project:
npx create-next-app@latest
For more installation details, you can refer to the official guide.
Building the Session Library
To make it easier to understand Server Actions, we will first create a simplified session system. This version will use JSON to store session data in cookies.
Create the session/index.ts
file and add the following code:
"use server";
import { cookies } from 'next/headers';
export type Session = {
username: string;
};
export const getSession = async (): Promise<Session | null> => {
const cookieStore = cookies();
const session = cookieStore.get('session');
if (session?.value) {
return JSON.parse(session.value) as Session;
}
return null;
};
export const setSession = async (session: Session) => {
const cookieStore = cookies();
cookieStore.set('session', JSON.stringify(session));
};
export const removeSession = async () => {
const cookieStore = cookies();
cookieStore.delete('session');
};
The first line of the code "use server"
marks the functions in this file as Server Actions. Since direct access to request
and response
is not possible, next/headers
is used here to read and write cookies, and this functionality is only available in Server Actions.
Implementing Two New Server Actions
After having the session library, the next step is to implement the login and logout functions to demonstrate the usability of the session system.
Add the following code to the user/index.ts
file:
"use server";
import { removeSession, setSession } from '@/session';
export const signIn = async (username: string) => {
await setSession({ username });
};
export const signOut = async () => {
await removeSession();
};
Here, a simplified login process is adopted, and logging in only requires entering the username.
Building the Page
In the App Router, pages are usually asynchronous components, and Server Actions cannot be directly called from such components. You need to create components using "use client"
:
components/sign-in.tsx
"use client";
import { signIn } from '@/user';
import { useState } from'react';
const SignIn = () => {
const [username, setUsername] = useState('');
return (
<div>
<input
type="text"
value={username}
placeholder="username"
onChange={(event) => {
setUsername(event.target.value);
}}
/>
<button
disabled={!username}
onClick={() => {
signIn(username);
}}
>
Sign In
</button>
</div>
);
};
export default SignIn;
components/sign-out.tsx
"use client";
import { signOut } from '@/user';
const SignOut = () => {
return (
<button
onClick={() => {
signOut();
}}
>
Sign Out
</button>
);
};
export default SignOut;
Finally, build the app/page.tsx
:
import { getSession } from '@/session';
import styles from './page.module.css';
import SignIn from '../components/sign-in';
import SignOut from '@/components/sign-out';
export default async function Home() {
const session = await getSession();
return (
<main className={styles.main}>
{session? (
<div>
<div>You are logged in as {session.username}</div>
<div>
<SignOut />
</div>
</div>
) : (
<SignIn />
)}
</main>
);
}
Implementing Encryption
After implementing the relevant functions of Server Actions, the last step is to implement the encryption function, which can be completed through the crypto
module.
session/encrypt.ts
import { createCipheriv, createDecipheriv } from 'crypto';
// Replace with your own key and iv
// You can generate them using crypto.randomBytes(32) and crypto.randomBytes(16)
const key = Buffer.from('17204a84b538359abe8ba74807efa12a068c20a7c7f224b35198acf832cea57b', 'hex');
const iv = Buffer.from('da1cdcd9fe4199c835bd5f1d56446aff', 'hex');
const algorithm = 'aes-256-cbc';
export const encrypt = (text: string) => {
const cipher = createCipheriv(algorithm, key, iv);
const encrypted = cipher.update(text, 'utf8', 'base64');
return `${encrypted}${cipher.final('base64')}`;
};
export const decrypt = (encrypted: string) => {
const decipher = createDecipheriv(algorithm, key, iv);
const decrypted = decipher.update(encrypted, 'base64', 'utf8');
return `${decrypted}${decipher.final('utf8')}`;
};
Subsequently, modify the session library to implement the encryption function:
"use server";
import { cookies } from 'next/headers';
import { decrypt, encrypt } from './encrypt';
export type Session = {
username: string;
};
export const getSession = async (): Promise<Session | null> => {
const cookieStore = cookies();
const session = cookieStore.get('session');
if (session?.value) {
try {
const decrypted = decrypt(session.value);
return JSON.parse(decrypted) as Session;
} catch {
// Ignore invalid sessions
}
}
return null;
};
export const setSession = async (session: Session) => {
const cookieStore = cookies();
const encrypted = encrypt(JSON.stringify(session));
cookieStore.set('session', encrypted);
};
export const removeSession = async () => {
const cookieStore = cookies();
cookieStore.delete('session');
};
Leapcell: The Best of Serverless Web Hosting
Finally, I recommend a platform that is most suitable for deploying web services: Leapcell