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.' 
    }
  }
}

🧠 Test Your Knowledge

What directive marks a function as a Server Action?