In Next.js, data mutations—such as adding items, logging out, or updating a database—are handled using Server Actions. These actions are treated as public HTTP endpoints and must be secured accordingly with proper validation and authorization.
IDs are regenerated every 14 days or when the build cache is invalidated. Despite these protections, Server Actions should be treated like public endpoints and secured accordingly.
Client inputs such as form data, URL parameters, headers, and searchParams must be validated. Example:
// BAD: trusting searchParams directly
const isAdmin = searchParams.get('isAdmin')
if (isAdmin === 'true') return <AdminPanel />
// GOOD: verify using server-side token
const token = cookies().get('AUTH_TOKEN')
const isAdmin = await verifyAdmin(token)
if (isAdmin) return <AdminPanel />Every Server Action should verify that the user is authorized to perform the mutation:
'use server'
import { auth } from './lib'
export function addItem() {
const { user } = auth()
if (!user) throw new Error('You must be signed in to perform this action')
// ...
}Defining a Server Action inside a component creates a closure that captures outer variables. Example:
export default async function Page() {
const publishVersion = await getLatestVersion()
async function publish() {
"use server"
if (publishVersion !== await getLatestVersion()) {
throw new Error('Version has changed')
}
}
return (
<form>
<button formAction={publish}>Publish</button>
</form>
)
}Next.js encrypts closed-over variables and generates a new private key for each build. However, encryption alone should not be relied on to protect sensitive data.
For self-hosted deployments, use NEXT_SERVER_ACTIONS_ENCRYPTION_KEY to ensure consistent encryption across servers. The key must be AES-GCM encrypted.
Server Actions use POST requests and compare the Origin header with Host or X-Forwarded-Host. If they don’t match, the request is aborted. You can configure allowed origins:
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
},
},
}Mutations should not occur during rendering. Example of incorrect usage:
// BAD
if (searchParams.get('logout')) {
cookies().delete('AUTH_TOKEN')
}Correct approach using Server Actions:
// GOOD
import { logout } from './actions'
<form action={logout}>
<button type="submit">Logout</button>
</form>Server Actions in Next.js offer a powerful way to handle mutations, but they must be secured with proper validation, authorization, and encryption. Avoid side effects during rendering, and audit your application regularly to ensure data integrity and protection.