Slide 1
-----------
Part 1: Let's talk about how you can update your data using React's server actions.
Slide 2
-----------
Part 1: In this course we are building a blogging app where users read posts filtered by tags like 'tech' or 'travel,' and admins add or edit posts. Server Actions in Next.js make this smooth by letting us update data directly from components—no page reloads, no messy API calls.
Part 2: Without Server Actions, adding a post might mean sending a request, waiting, and refreshing the page. That’s slow and clunky—users hate waiting. Server Actions fix this by running on the server with one call.
Part 3: Server Actions are async functions that run server-side. Call them from a form or button, and they update data instantly. It’s like giving Blog superpowers—fast, secure, and simple!
Part 4: You might think client-side code can do it all. Nope! That’s not secure for sensitive tasks like saving posts. Server Actions keep the heavy lifting on the server.
Slide 3
-----------
Part 1: Server Actions are special functions in Next.js marked with 'use server'. They run on the server, not the browser, and handle data mutations—like adding a post to BlogSphere.
Part 2: Let’s say an admin adds a post titled 'Why AI is Awesome'. With Server Actions, we define one function, call it from a form, and it saves the post—no extra steps!
Part 3: Here’s our first Server Action for Blog. It’s basic but shows the core idea. This Server Action takes form data, extracts the title, content, and tag, and logs them—pretending it’s a database save. You’d call this from a form in Blog's admin page. It’s server-side, so it’s secure and fast.
Part 4: Server actions must have the "use server" keyword as a first experssion in the file.
Part 5: These lines grab the form data—title, content, and tag. In BlogSphere, this is how we collect what the admin types, solving the problem of getting user input to the server.
Part 6: This logs the post, simulating a save. It’s our first step to mutating data, showing how Server Actions handle the core task of updating Blog
Slide 4
-----------
Part 1: Let’s build the admin side of Blog. This form lets admins add posts with a title, content, and tag. The Server Action ties it all together. The form calls addPost, which validates the title and logs the post. useActionState shows feedback—errors or success—without a reload. Let's break it down!
Part 2: This is the form where we collect user data. It is an uncontrolled form.
Part 3: Notice that instead of URL we provide function formAction to the action prop. This already is magic!
Part 4: The formAction is created using the new useActionState hook, which takes a server action addPost as its parameter and allows us to track its asynchronous execution.
Part 5: The server action addPost reads the form data and parses them to variables
Part 6: We then validate the data. This is using arbitrary validation, but there are sophisticated validation libraries, such as Zod, that you can use. Please note that we return JSON with success set to false and the error message. This is how we communicate results to the client.
Part 7: We create and log the new post. Here you would store it in the database.
Part 8: Last, we tell Next.js to invalidate the route /posts, as adding the new posts should vacate the cached data and load a new list.
Part 9: We return a JSON that the action executed correctly
Slide 5
-----------
Part 1: But, as an absolute magic trick, you can call server function from the client!
Part 2: Consider this simple function that save a draft of the text you are working on.
Part 3: On client, we add an asynchronous function that calls this server action ... yes, you heard it well ... we can call function on the server from the client!
Part 4: We then hook this asynchronous handler to the onChange listener, so when user changes the value in the textarea it is automatically updated on the server.
Slide 6
-----------
Part 1: Server actions allow type safe calls, reducing need for complicated REST structures.
Part 2: On the other hand, it disables access to external users who might want to use this endpoint as well. Thus, it is not useful when creating public facing APIs.
Slide 7
-----------
Part 1: Sometimes, server actions take too long, but you can anticipate their result. Therefore, you can use optimistic updates that update the client prior to action returning a value. This form optimistically adds a post—users see it immediately, and the server confirms it.
Part 2: This is our server action, that creates the new post.
Part 3: useOptimistic sets up instant updates. It takes initial posts and a function to add new ones, solving the problem of slow UI updates in Blog
Part 4: We create a post and add it optimistically before the server call. This makes Blog feel snappy, tackling user wait time.
Part 5: We use the optimistic value to render the list, not the original value.
Slide 8
-----------
Part 1: Server Actions let you mutate data—like adding posts—right from components. They’re secure, fast, and tie into Next.js caching.
Part 2: Always validate data server-side, use revalidatePath for fresh UI, and test optimistic rollbacks if the server fails.
Part 3: Don’t skip validation—bad data crashes apps. Keep arguments simple for serialization. Practice mixing client and server code—it’s tricky but worth it!
SERVER ACTIONS AND MUTATIONS
Hey everyone! Today, we’re diving into something super cool and powerful in web
development: Server Actions and Mutations. If you’ve ever wondered how websites
update information—like adding a blog post or liking a comment—without needing
to refresh the page, this is the magic behind it. In this lecture, we’ll explore
how Server Actions work in Next.js, a popular framework, and why they’re a
game-changer for building interactive apps.
Why does this matter? Imagine you’re running a blogging app—let’s call it
"BlogSphere." Users read posts, filter them by tags like "tech" or "travel," and
admins add or edit posts. Without Server Actions, every time an admin adds a
post, the page might need to reload, or you’d need complicated code to talk to
the server. Server Actions make this smooth and simple, letting you update data
directly from your components. By the end of this lecture, you’ll see how to use
them to build BlogSphere and handle real-world tasks like creating posts or
updating drafts.
Let’s hook you in with a question: Have you ever thought adding a blog post
should require a bunch of separate server requests? A common misconception is
that you need tons of API calls to update a site. Surprise—Server Actions let
you do it in one go! Or maybe you think client-side code alone can handle
everything? Nope, that’s not always secure or efficient. Let’s break these ideas
apart and see the truth.
--------------------------------------------------------------------------------
SECTION 1: WHAT ARE SERVER ACTIONS?
So, what exactly are Server Actions? They’re special functions in Next.js that
run on the server, not the browser. You can call them from your app—whether it’s
a button click or a form submission—and they handle tasks like saving data (we
call this "mutating" data). They’re asynchronous, meaning they don’t freeze your
app while they work.
Let’s start with BlogSphere. Imagine a user wants to see all posts tagged
"tech." On the admin side, you want to add a new post titled "Why AI is
Awesome." Without Server Actions, you might write a separate API endpoint, send
a request, wait for a response, and then update the page. With Server Actions,
you define a function, mark it with 'use server', and call it directly. It’s
like giving your app a superpower to talk to the server instantly.
Here’s a simple example for BlogSphere:
async function addPost(formData: FormData) {
'use server';
const title = formData.get('title');
const content = formData.get('content');
// Save to database (pretend we have one!)
console.log(`New post: ${title} - ${content}`);
}
You’d use this in a form, and boom—the post is added without extra hassle.
Later, we’ll make this more complex, but this is the foundation.
Why is this surprising? Many think server stuff has to be separate from the UI.
Server Actions blur that line, letting you write server code right next to your
components. It’s efficient and keeps things tidy.
--------------------------------------------------------------------------------
SECTION 2: BUILDING BLOGSPHERE WITH SERVER ACTIONS
Let’s build BlogSphere step-by-step. We’ll start with the admin adding a post,
then make it fancier.
SIMPLE EXAMPLE: ADDING A POST
Picture an admin dashboard with a form: fields for "Title," "Content," and
"Tag." When the admin submits it, we want the post saved and the page updated
without a reload. Here’s how it works:
// app/actions.ts
'use server';
export async function addPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
const tag = formData.get('tag') as string;
// Imagine this saves to a database
const newPost = { id: Date.now(), title, content, tag, date: new Date() };
console.log('Post added:', newPost);
return newPost;
}
// app/admin/add-post/page.tsx
export default function AddPostPage() {
async function handleAddPost(formData: FormData) {
'use server';
await addPost(formData); // Reuse the action
}
return (
<form action={handleAddPost}>
<input name="title" placeholder="Post Title" />
<textarea name="content" placeholder="Write your post..." />
<input name="tag" placeholder="Tag (e.g., tech)" />
<button type="submit">Add Post</button>
</form>
);
}
What’s happening? The form calls handleAddPost, which runs on the server and
uses addPost to save the data. No page refresh needed—Next.js handles the update
behind the scenes.
ADVANCED EXAMPLE: VALIDATION AND FEEDBACK
Now, let’s level up. What if the admin forgets the title? We’ll add server-side
validation and give feedback. Plus, we’ll revalidate the cache so users see the
new post instantly.
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function addPost(prevState: any, formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
const tag = formData.get('tag') as string;
// Validation
if (!title || title.length < 3) {
return { error: 'Title must be at least 3 characters long' };
}
const newPost = { id: Date.now(), title, content, tag, date: new Date() };
console.log('Post added:', newPost);
revalidatePath('/posts'); // Refresh the posts page
return { success: true, post: newPost };
}
// app/admin/add-post/page.tsx
'use client';
import { useActionState } from 'react';
import { addPost } from '@/app/actions';
export default function AddPostPage() {
const [state, formAction] = useActionState(addPost, { error: null, success: false });
return (
<form action={formAction}>
<input name="title" placeholder="Post Title" />
<textarea name="content" placeholder="Write your post..." />
<input name="tag" placeholder="Tag (e.g., tech)" />
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
{state.success && <p style={{ color: 'green' }}>Post added successfully!</p>}
<button type="submit">Add Post</button>
</form>
);
}
Here, we use useActionState to show errors or success messages. If the title’s
too short, the server sends back an error. If it works, the cache updates, and
users see the new post on /posts. Cool, right?
HOW IT WORKS
Server Actions run on the server, so they’re secure—no sensitive logic leaks to
the browser. They integrate with Next.js caching, meaning one call can update
data and the UI. They’re also reusable—call addPost from a form, a button, or
even a keyboard shortcut.
Advantages:
* Fast: One server trip does it all.
* Secure: Runs server-side, away from prying eyes.
* Simple: No need for separate APIs.
Disadvantages:
* Serialization: Arguments must be simple (no complex objects).
* Learning curve: Mixing server and client code can confuse beginners.
--------------------------------------------------------------------------------
SECTION 3: EXPANDING BLOGSPHERE
Let’s push BlogSphere further. Users want to filter posts by tag, and admins
want to save drafts.
FILTERING POSTS
On the user side, we’ll display posts and let them filter by tag. Server Actions
can help fetch filtered data:
// app/actions.ts
'use server';
export async function getPostsByTag(tag: string) {
const allPosts = [
{ id: 1, title: 'AI Basics', tag: 'tech', date: new Date() },
{ id: 2, title: 'Travel Tips', tag: 'travel', date: new Date() },
]; // Pretend database
return allPosts.filter(post => post.tag === tag);
}
// app/posts/[tag]/page.tsx
export default async function PostsByTag({ params }: { params: { tag: string } }) {
const posts = await getPostsByTag(params.tag);
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title} - {post.tag}</li>
))}
</ul>
);
}
Here, getPostsByTag runs on the server, fetching posts for a tag like "tech."
The page renders them directly.
Saving Drafts
Admins might want to save a draft without publishing. Let’s add an event
handler:
// app/actions.ts
'use server';
export async function saveDraft(content: string) {
console.log('Draft saved:', content);
return { success: true };
}
// app/admin/edit-post/page.tsx
'use client';
import { saveDraft } from '@/app/actions';
export default function EditPost() {
return (
<form>
<textarea
name="content"
onChange={async (e) => {
await saveDraft(e.target.value);
}}
/>
<button type="submit">Publish</button>
</form>
);
}
Every keystroke saves the draft on the server. We could debounce this (wait a
bit between saves) to avoid overloading, but this shows the flexibility—Server
Actions work beyond forms!
--------------------------------------------------------------------------------
SECTION 4: OPTIMISTIC UPDATES IN BLOGSPHERE
Now, let’s make BlogSphere feel really fast with optimistic updates. Normally,
when an admin adds a post, they wait for the server to confirm it’s saved before
seeing it on the screen. What if we show it instantly and update it behind the
scenes? That’s optimistic updating—assuming success and fixing it later if
something goes wrong.
Imagine the admin adds "Why AI is Awesome" on the /posts page, where users also
see the latest posts. We’ll use the useOptimistic hook to show the post
immediately:
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function addPostOptimistic(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
const tag = formData.get('tag') as string;
if (!title || title.length < 3) {
throw new Error('Title must be at least 3 characters long');
}
const newPost = { id: Date.now(), title, content, tag, date: new Date() };
console.log('Post added:', newPost);
revalidatePath('/posts');
return newPost;
}
// app/posts/page.tsx
'use client';
import { useOptimistic } from 'react';
import { addPostOptimistic } from '@/app/actions';
type Post = { id: number; title: string; content: string; tag: string; date: Date };
export default function PostsPage({ initialPosts }: { initialPosts: Post[] }) {
const [optimisticPosts, addOptimisticPost] = useOptimistic<Post[], Post>(
initialPosts,
(state, newPost) => [...state, newPost]
);
async function handleAddPost(formData: FormData) {
const newPost = {
id: Date.now(),
title: formData.get('title') as string,
content: formData.get('content') as string,
tag: formData.get('tag') as string,
date: new Date(),
};
addOptimisticPost(newPost); // Show instantly
await addPostOptimistic(formData); // Confirm with server
}
return (
<div>
<ul>
{optimisticPosts.map(post => (
<li key={post.id}>{post.title} - {post.tag}</li>
))}
</ul>
<form action={handleAddPost}>
<input name="title" placeholder="Post Title" />
<textarea name="content" placeholder="Write your post..." />
<input name="tag" placeholder="Tag (e.g., tech)" />
<button type="submit">Add Post</button>
</form>
</div>
);
}
HOW IT WORKS
The useOptimistic hook takes the current posts and a function to update them.
When the admin submits the form, we:
1. Instantly add the post to optimisticPosts—it appears on the screen.
2. Call addPostOptimistic to save it on the server.
3. If the server fails (e.g., validation error), we could roll back, but here
we assume success for simplicity.
ADVANTAGES AND CHALLENGES
* Advantage: Feels instant—users love speed! Great for likes, comments, or
posts.
* Advantage: Works with Server Actions’ server-side power.
* Challenge: If the server fails, you need a plan to undo the update (we’ll
save that for later).
* Challenge: Requires client-side code, so it’s not purely server-driven.
In BlogSphere, optimistic updates make adding posts snappy, keeping users
engaged. It’s like saying, “Trust me, it’s saved,” while the server catches up.
--------------------------------------------------------------------------------
CONCLUSION
Let’s wrap up! Server Actions in Next.js let you mutate data—like adding posts
or saving drafts—right from your components. They’re fast, secure, and tie into
caching for instant updates. With optimistic updates, they feel even faster. In
BlogSphere, we added posts, validated them, filtered by tags, saved drafts, and
made updates feel instant—all with a few functions.
Key takeaways:
* Use 'use server' to define Server Actions.
* They handle form submissions, event handlers, and more.
* Validate data server-side for safety.
* Revalidate caches to keep the UI fresh.
* Optimistic updates boost perceived speed.
Common pitfalls to avoid:
* Don’t skip validation—users might send bad data.
* Watch out for serialization limits—keep arguments simple.
* Test client-server boundaries; mixing them takes practice.
* Plan for server failures with optimistic updates.
Next time you’re building an app, think: Can Server Actions simplify this? Can
optimistic updates make it feel faster? For BlogSphere, they turned a messy
process into a smooth, snappy one. You’ve got the tools—go experiment and build
something awesome!
Maggie is a generative AI that can help you understand the course content better. You can ask her questions about the lecture, and she will try to answer them. You can also see the questions asked by other students and her responses.
Join the discussion to ask questions, share your thoughts, and discuss with other learners