Server Actions
Execute server-side code from client components
⚡ What are Server Actions?
Server Actions are asynchronous functions that run on the server but can be called directly from client components. They simplify form submissions and data mutations without creating API routes.
// actions.js
'use server'
export async function createPost(formData) {
const title = formData.get('title')
// Save to database
return { success: true }
}
// Use in form:
// <form action={createPost}>
Key Concepts
Server-side Only
Code runs securely on server
'use server'
async function saveData() {}
Form Integration
Works directly with HTML forms
<form action={serverAction}>
Progressive Enhancement
Works without JavaScript enabled
// Forms work even if JS fails
Automatic Revalidation
Updates data automatically
revalidatePath('/posts')
🔹 Basic Server Action
Server Actions are defined with the 'use server' directive. They can be used directly in form actions for seamless server-side processing.
// app/actions.js
'use server'
export async function createUser(formData) {
const name = formData.get('name')
const email = formData.get('email')
// Save to database
console.log('Creating user:', name, email)
return { success: true, message: 'User created!' }
}
// app/page.js
import { createUser } from './actions'
export default function Page() {
return (
<form action={createUser}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Create User</button>
</form>
)
}
🔹 Server Action with useFormState
Use the useFormState hook to handle loading states and display feedback messages from server actions in your forms.
// app/actions.js
'use server'
export async function submitForm(prevState, formData) {
const message = formData.get('message')
if (!message) {
return { error: 'Message is required' }
}
// Process the message
return { success: 'Message sent successfully!' }
}
// app/form.js
'use client'
import { useFormState } from 'react-dom'
import { submitForm } from './actions'
export default function ContactForm() {
const [state, formAction] = useFormState(submitForm, null)
return (
<form action={formAction}>
<textarea name="message" placeholder="Your message" />
<button type="submit">Send</button>
{state?.error && <p style={{color: 'red'}}>{state.error}</p>}
{state?.success && <p style={{color: 'green'}}>{state.success}</p>}
</form>
)
}
🔹 Inline Server Actions
You can define server actions directly inside server components without creating a separate file. Just add 'use server' inside the function.
// app/page.js (Server Component)
export default function TodoPage() {
async function addTodo(formData) {
'use server'
const task = formData.get('task')
// Save to database
console.log('Adding todo:', task)
}
return (
<form action={addTodo}>
<input name="task" placeholder="New todo" required />
<button type="submit">Add Todo</button>
</form>
)
}
🔹 Revalidating Data
After mutating data, use revalidatePath or revalidateTag to update cached data and refresh the UI automatically.
// app/actions.js
'use server'
import { revalidatePath } from 'next/cache'
export async function deletePost(postId) {
// Delete from database
await db.posts.delete(postId)
// Refresh the posts page
revalidatePath('/posts')
return { success: true }
}
// Usage in component:
// <button onClick={() => deletePost(post.id)}>Delete</button>
🔹 Server Actions with useFormStatus
The useFormStatus hook provides information about form submission status, perfect for showing loading states on submit buttons.
// app/submit-button.js
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
)
}
// app/page.js
import { SubmitButton } from './submit-button'
export default function Page() {
async function handleSubmit(formData) {
'use server'
// Process form
}
return (
<form action={handleSubmit}>
<input name="data" />
<SubmitButton />
</form>
)
}
🔹 Error Handling
Handle errors gracefully in server actions by using try-catch blocks and returning error states to the client.
// app/actions.js
'use server'
export async function updateProfile(formData) {
try {
const name = formData.get('name')
if (!name || name.length < 2) {
return {
error: 'Name must be at least 2 characters'
}
}
// Update database
await db.users.update({ name })
return {
success: true,
message: 'Profile updated!'
}
} catch (error) {
console.error('Update failed:', error)
return {
error: 'Failed to update profile. Please try again.'
}
}
}