async function getUserData() {
// This fetch request runs on the server
const res = await fetch('https://api.example.com/user/123', { cache: 'no-store' }); // Example: no-store for dynamic data
if (!res.ok) {
throw new Error('Failed to fetch user data');
}
return res.json();
}
export default async function DashboardPage() {
const userData = await getUserData(); // Await the data fetching
// The component will only start rendering after userData is resolved
return (
<div>
<h1>Welcome, {userData.name}</h1>
<p>Email: {userData.email}</p>
{/* ... more content */}
</div>
);
}
// app/products/[id]/page.tsx
import { db } from '@/lib/db'; // Assuming your database connection
interface ProductPageProps {
params: { id: string };
}
export default async function ProductPage({ params }: ProductPageProps) {
const productId = params.id;
const product = await db.product.findUnique({ // Directly query database
where: { id: productId },
});
if (!product) {
// Handle product not found
return <div>Product not found</div>;
}
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
// app/docs/[slug]/page.tsx
import fs from 'fs/promises';
import path from 'path';
interface DocPageProps {
params: { slug: string };
}
export default async function DocPage({ params }: DocPageProps) {
const filePath = path.join(process.cwd(), 'docs', `${params.slug}.md`);
let content = '';
try {
content = await fs.readFile(filePath, 'utf8');
} catch (error) {
// Handle file not found or read error
console.error('Error reading doc file:', error);
return <div>Document not found.</div>;
}
return (
<article>
<h1>{params.slug}</h1>
<pre>{content}</pre>
</article>
);
}
// app/items/actions.ts (a separate file for server actions)
'use server';
import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';
export async function addItem(formData: FormData) {
const name = formData.get('name') as string;
await db.item.create({ data: { name } }); // Async DB operation
revalidatePath('/items'); // Revalidate cache for the items page
}
Example
actions.ts
// app/todos/actions.ts
'use server'; // ⭐ 서버 액션임을 나타내는 필수 지시어
import { revalidatePath } from 'next/cache'; // 캐시 재검증을 위한 함수
// 💡 가상의 데이터 저장소 (실제 앱에서는 데이터베이스가 이 역할을 합니다)
interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: Date;
}
const todos: Todo[] = [
{ id: '1', text: '예제 할 일 1', completed: false, createdAt: new Date() },
{ id: '2', text: '예제 할 일 2 (완료)', completed: true, createdAt: new Date() },
];
/**
* 모든 할 일 목록을 가져오는 함수 (서버 컴포넌트에서 호출)
* 이 함수는 서버 액션이 아니지만, 서버에서만 사용됨
*/
export async function getTodosList(): Promise<Todo[]> {
// 실제 데이터베이스 쿼리 대신 가상 데이터 반환
return todos.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
}
/**
* 새로운 할 일을 데이터 저장소에 추가하는 서버 액션
* @param formData - 폼에서 전송된 데이터
*/
export async function addTodo(formData: FormData) {
const todoText = formData.get('todoText') as string;
if (!todoText || todoText.trim() === '') {
return { error: '할 일 내용을 입력해주세요.' };
}
const newTodo: Todo = {
id: String(Date.now()), // 간단한 고유 ID 생성
text: todoText.trim(),
completed: false,
createdAt: new Date(),
};
todos.push(newTodo); // 가상 데이터 저장소에 추가
// 특정 경로의 캐시를 재검증하여 해당 페이지가 다시 렌더링되도록 합니다.
// 여기서는 할 일 목록이 표시되는 /todos 경로를 재검증합니다.
revalidatePath('/todos');
return { success: true }; // 성공 시 반환
}
/**
* 할 일의 완료 상태를 토글하는 서버 액션
*/
export async function toggleTodoCompleted(id: string) {
// `'use server'` 지시어는 파일 최상단에 있어도 개별 함수에도 명시할 수 있습니다.
// 명시적으로 표시하는 것이 유지보수에 도움이 될 수 있습니다.
'use server';
const todo = todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed; // 상태 토글
revalidatePath('/todos'); // 캐시 재검증
} else {
return { error: '할 일을 찾을 수 없습니다.' };
}
return { success: true };
}
ListPage.tsx (SSR)
import { getTodosList, addTodo, toggleTodoCompleted } from './actions'; // 서버 액션 및 가상 데이터 가져오기 함수 임포트
export default async function TodosPage() {
const todos = await getTodosList(); // 서버에서 직접 할 일 목록 가져오기
return (<></>)
}
Form.tsx(CSR + Server Action Function to revalidate SSR Page)
import { addTodo } from './actions'; // 서버 액션 임포트
export default function TodoForm() {
const formRef = useRef<HTMLFormElement>(null); // 폼 요소를 참조하여 초기화
// 클라이언트에서 서버 액션을 호출하고 결과를 처리하는 함수
const clientAction = async (formData: FormData) => {
const result = await addTodo(formData); // 서버 액션 호출 및 결과 대기
if (result?.error) {
alert(result.error); // 서버 액션에서 반환된 에러 메시지 표시
} else {
formRef.current?.reset(); // 성공 시 폼 입력 필드 초기화
}
};
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(res => res.json());
return posts.map((post: { slug: string }) => ({ slug: post.slug }));
}