
서버 액션이란?
서버에서 실행되는 비동기 함수를 브라우저에서 호출하는 방법
과정
1. 브라우저에서 특정 form에 대한 submit 이벤트 발생
2. 서버에서만 실행되는 함수를 브라우저가 직접 호출
3. 데이터를 FormData 형식으로 전달
function ReviewEditor() {
return (
<section>
<form>
<input name='content' placeholder='리뷰 내용' />
<input name='author' placeholder='작성자' />
<button type='submit'>작성하기</button>
</form>
</section>
);
}
function ReviewEditor() {
async function createReviewAction() {
'use server';
}
return (
...
)
}
<form action={createReviewAction}>
...
</form>
→ 서버액션을 만들면 자동으로 API 생성 + 폼태그 제출시 해당 API가 자동 호출
props를 통해 FormData를 전달받아 사용할 수 있다
async function createReviewAction(formData: FormData) {
'use server';
console.log(formData);
}
async function createReviewAction(formData: FormData) {
'use server';
const content = formData.get('content')?.toString()
const author = formData.get('author')?.toString();
}
?.toString() → 해당 값이 있는 경우에만 get메서드를 호출 + 값을 문자열로 변환
기존에 사용하던 백엔드 서버의 API를 활용한다
try ~ catch 활용try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/review`,
{
method: 'POST',
body: JSON.stringify({ bookId, content, author }),
}
);
console.log(response.status);
} catch (err) {
console.error(err);
return;
}
src/actions 폴더 생성src/actions/create-review.action.ts 파일 생성use server 키워드를 최상단으로 이동bookId를 FormData에서 추출할 수 있도록 설정// 📄 src/actions/create-review.action.ts
'use server';
export async function createReviewAction(formData: FormData) {
const bookId = formData.get('bookId')?.toString();
const content = formData.get('content')?.toString();
const author = formData.get('author')?.toString();
if (!content || !author) {
return;
}
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/review`,
{
method: 'POST',
body: JSON.stringify({ bookId, content, author }),
}
);
console.log(response.status);
} catch (err) {
console.error(err);
return;
}
}
createReviewAction을 import // 📄 src/app/book/[id]/page.tsx
import { createReviewAction } from '@/actions/create-review.action';
bookId를 받아오기 위해 form 태그 안에 input 코드 추가 (일종의 트릭)hidden → input 태그를 브라우저로 부터 감춰준다function ReviewEditor({ bookId }: { bookId: string }) {
return (
<section>
<form action={createReviewAction}>
<input name='bookId' value={bookId} hidden />
...
input 태그는 총 3개지만 화면에는 input이 2개만 나타나고
form 제출시 bookId, content, author 3개의 FormData가 확인된다
입력한 데이터는
백엔드 서버 터미널에서 $ npx prisma studio → http://localhost:5555/ 접속하여 확인할 수 있다
최종 코드
// 📄 src/app/book/[id]/page.tsx
import { notFound } from 'next/navigation';
import style from './page.module.css';
import { createReviewAction } from '@/actions/create-review.action';
export function generateStaticParams() {
return [{ id: '1' }, { id: '2' }, { id: '3' }];
}
async function BookDetail({ bookId }: { bookId: string }) {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book/${bookId}`
);
if (!response.ok) {
if (response.status === 404) {
notFound();
}
return <div>오류가 발생했습니다 ...</div>;
}
const book = await response.json();
const {
id,
title,
subTitle,
description,
author,
publisher,
coverImgUrl,
} = book;
return (
<section>
<div
className={style.cover_img_container}
style={{ backgroundImage: `url('${coverImgUrl}')` }}
>
<img src={coverImgUrl} />
</div>
<div className={style.title}>{title}</div>
<div className={style.subTitle}>{subTitle}</div>
<div className={style.author}>
{author} | {publisher}
</div>
<div className={style.description}>{description}</div>
</section>
);
}
function ReviewEditor({ bookId }: { bookId: string }) {
return (
<section>
<form action={createReviewAction}>
<input name='bookId' value={bookId} hidden />
<input required name='content' placeholder='리뷰 내용' />
<input required name='author' placeholder='작성자' />
<button type='submit'>작성하기</button>
</form>
</section>
);
}
export default function Page({ params }: { params: { id: string } }) {
return (
<div className={style.container}>
<BookDetail bookId={params.id} />
<ReviewEditor bookId={params.id} />
</div>
);
}
// 📄 create-review.action.ts
'use server';
export async function createReviewAction(formData: FormData) {
const bookId = formData.get('bookId')?.toString();
const content = formData.get('content')?.toString();
const author = formData.get('author')?.toString();
if (!content || !author) {
return;
}
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/review`,
{
method: 'POST',
body: JSON.stringify({ bookId, content, author }),
}
);
console.log(response.status);
} catch (err) {
console.error(err);
return;
}
}