Server actions in Next.js are essential for handling secure, efficient server-side operations such as data fetching, form submissions, and API interactions. These actions enable you to shift logic to the server, ensuring optimal performance and security without exposing sensitive data to the client.
In this blog, we’ll explore server actions through real-world examples.
What Are Server Actions?
Server actions allow server-side functions to handle operations such as:
- Fetching data from APIs or databases: The data fetching occurs on the server, keeping the logic hidden and reducing the load on the client.
- Processing forms: Server actions securely process form submissions without exposing sensitive information.
- Managing secure operations: Actions like authentication and database queries can be handled server-side to maintain security and performance.
These operations run exclusively on the server, ensuring that sensitive logic and credentials are kept safe while delivering pre-rendered data to the client.
Example 1: Data Fetching for Dynamic Content
One of the most common use cases for server actions is fetching data from an API or a database. Let’s look at how to implement a server-side data fetching action for a product page.
// app/product/[id]/page.tsx
interface Product {
id: string;
name: string;
description: string;
price: number;
}
export const getProduct = async (id: string): Promise<Product> => {
const res = await fetch(`https://api.example.com/products/${id}`);
if (!res.ok) throw new Error('Failed to fetch product data');
return res.json();
};
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProduct(params.id);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
);
}
In this example:
- The getProduct function runs server-side, fetching data securely from the API without exposing the logic to the client.
- When the page is loaded, the product data is fetched on the server and then rendered.
Example 2: Secure Form Submission with Server Actions
Handling form submissions on the server allows for better security and performance, especially when dealing with sensitive data or complex validations. Below is an example of how server actions can handle form submissions.
// app/contact/page.tsx
interface ContactFormData {
name: string;
email: string;
message: string;
}
export const sendFormData = async (data: ContactFormData): Promise<void> => {
const res = await fetch('https://api.example.com/contact', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
},
});
if (!res.ok) throw new Error('Form submission failed');
};
export default function ContactForm() {
const handleSubmit = async (formData: FormData) => {
'use server'; // Ensures the action executes server-side
const contactData: ContactFormData = {
name: formData.get('name') as string,
email: formData.get('email') as string,
message: formData.get('message') as string,
};
await sendFormData(contactData);
};
return (
<form action={handleSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required></textarea>
<button type="submit">Send</button>
</form>
);
}
Key details:
- The sendFormData function handles form submissions server-side, ensuring sensitive user data isn’t exposed to the client.
- The handleSubmit function uses 'use server' to guarantee the form is processed on the server.
Example 3: Authentication with Server Actions
Authentication is a sensitive operation, and server actions are perfect for handling login logic securely. Here’s an example of how to manage authentication using server actions.
// app/login/page.tsx
interface LoginCredentials {
username: string;
password: string;
}
interface AuthResponse {
token: string;
}
export const authenticateUser = async (credentials: LoginCredentials): Promise<AuthResponse> => {
const res = await fetch('https://auth.example.com/login', {
method: 'POST',
body: JSON.stringify(credentials),
headers: {
'Content-Type': 'application/json',
},
});
if (!res.ok) throw new Error('Authentication failed');
return res.json();
};
export default function LoginPage() {
const handleLogin = async (formData: FormData) => {
'use server';
const credentials: LoginCredentials = {
username: formData.get('username') as string,
password: formData.get('password') as string,
};
const authResponse = await authenticateUser(credentials);
document.cookie = `authToken=${authResponse.token}; path=/`;
};
return (
<form action={handleLogin}>
<input name="username" placeholder="Username" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit">Login</button>
</form>
);
}
In this scenario:
- The authenticateUser function is responsible for sending login credentials to the server and receiving a token.
- The handleLogin function processes the login form securely on the server, and the authentication token is stored in cookies.
Benefits of Server Actions
Improved Security: By handling critical operations such as data fetching and form submissions on the server, sensitive information is kept away from the client.
Optimized Performance: Server actions reduce the client’s workload, making your app more responsive and efficient.
Simplified Data Management: With server actions, you can fetch data before rendering, ensuring the client receives pre-rendered content without the need for additional data-fetching logic.
SEO Benefits: Since server actions pre-render content on the server, search engines can crawl and index your pages more effectively.
Conclusion
Server actions in Next.js provide an effective way to handle data fetching, form submissions, and other sensitive operations server-side. When combined with TypeScript, they become even more powerful, offering type safety, scalability, and enhanced developer experience.
Whether you’re fetching data from an API, processing forms, or managing authentication, server actions offer the perfect solution for secure and efficient operations in your Next.js applications.
Top comments (0)