form
을 사용하면 웹 애플리케이션에서 데이터를 생성하고 업데이트할 수 있습니다. Next.js는 서버 작업을 사용하여 양식 제출 및 데이터 변형을 처리하는 강력한 방법을 제공합니다.
서버 작업의 작동 방식
- 서버 작업을 사용하면 API 엔드포인트를 수동으로 생성할 필요가 없습니다. 대신 컴포넌트에서 직접 호출할 수 있는 비동기 서버 기능을 정의합니다.
- 서버 작업은 서버 컴포넌트에서 정의하거나 클라이언트 컴포넌트에서 호출할 수 있습니다. 서버 컴포넌트에서 작업을 정의하면 양식이 JavaScript 없이 작동하여 점진적인 향상을 제공할 수 있습니다.
- next.config.js 파일에서 서버 작업을 수행할 수 있습니다.
module.exports = {
experimental: {
serverActions: true,
},
}
- 알아두면 좋은 점
- 서버 컴포넌트에서 서버 작업을 호출하는 양식은 JavaScript 없이 작동할 수 있습니다.
- 클라이언트 컴포넌트에서 서버 작업을 호출하는 양식은 JavaScript가 아직 로드되지 않은 경우 제출을 대기열에 넣어 클라이언트 하이드레이션의 우선 순위를 지정합니다.
- 서버 작업은 사용되는 페이지나 레이아웃에서 런타임을 상속합니다.
- 현재 경로가 서버 작업을 사용하는 경우 동적으로 렌더링해야 합니다.
캐시된 데이터 재검증
- 서버 작업은 Next.js 캐싱 및 재검증 아키텍처와 긴밀하게 통합됩니다. form이 제출되면 서버 작업은 캐시된 데이터를 업데이트하고 변경해야 하는 모든 캐시 키의 유효성을 다시 검사할 수 있습니다.
- 기존 애플리케이션처럼 경로당 단일 form으로 제한되는 대신 서버 작업을 사용하면 경로당 여러 작업을 수행할 수 있습니다. 또한 form 제출 시 브라우저를 새로 고칠 필요가 없습니다. 단일 네트워크 왕복에서 Next.js는 업데이트된 UI와 새로 고쳐진 데이터를 모두 반환할 수 있습니다.
Example
- 서버 전용 form을 만들려면 서버 컴포넌트에서 서버 작업을 정의하세요. 작업은 함수 상단에 있는 "use server" 지시문을 사용하여 인라인으로 정의하거나 파일 상단에 지시문을 사용하여 별도의 파일에서 정의할 수 있습니다.
export default function Page() {
async function create(formData: FormData) {
'use server'
}
return <form action={create}>...</form>
}
<form action={create}>
는 FormData 데이터 유형을 사용합니다. 위의 예에서 HTML 양식을 통해 제출된 FormData는 서버 작업 만들기에서 액세스할 수 있습니다.
데이터 재검증
- 서버 작업을 사용하면 요청 시 Next.js 캐시를 무효화할 수 있습니다. revalidatePath를 사용하여 전체 경로 세그먼트를 무효화할 수 있습니다.
'use server'
import { revalidatePath } from 'next/cache'
export default async function submit() {
await submitForm()
revalidatePath('/')
}
- 또는 revalidateTag를 사용하여 캐시 태그로 특정 데이터 가져오기를 무효화합니다.
'use server'
import { revalidateTag } from 'next/cache'
export default async function submit() {
await addPost()
revalidateTag('posts')
}
Redirecting
- 서버 작업이 완료된 후 사용자를 다른 경로로 리디렉션하려면 리디렉션과 절대 또는 상대 URL을 사용할 수 있습니다.
'use server'
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export default async function submit() {
const id = await addPost()
revalidateTag('posts')
redirect(`/post/${id}`)
}
- 기본 form 유효성 검사에는 필수 및 type="email"과 같은 HTML 유효성 검사를 사용하는 것이 좋습니다.
- 고급 서버 측 유효성 검사를 위해 zod와 같은 스키마 유효성 검사 라이브러리를 사용하여 구문 분석된 양식 데이터의 구조를 유효성 검사합니다.
import { z } from 'zod'
const schema = z.object({
})
export default async function submit(formData: FormData) {
const parsed = schema.parse({
id: formData.get('id'),
})
}
로딩 상태 표시
- form이 서버에 제출될 때 로딩 상태를 표시하려면 useFormStatus 후크를 사용하십시오.
'use client'
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending}>{pending ? 'Submitting...' : 'Submit'}</button>
)
}
- 알아두면 좋은 점
- 현재 로드 또는 오류 상태를 표시하려면 클라이언트 컴포넌트를 사용해야 합니다. 서버 작업의 안정성이 향상됨에 따라 이러한 값을 검색하는 서버 측 함수에 대한 옵션을 탐색하고 있습니다.
에러 처리
- 서버 작업은 직렬화 가능한 개체를 반환할 수도 있습니다. 예를 들어 서버 작업은 새 항목 생성 시 발생하는 에러를 처리하고 성공 또는 오류 메시지를 반환할 수 있습니다.
'use server'
export async function create(formData: FormData) {
try {
await createItem(formData.get('item'))
revalidatePath('/')
return { message: 'Success!' }
} catch (e) {
return { message: 'There was an error.' }
}
}
- 그런 다음 클라이언트 컴포넌트에서 이 값을 읽고 상태에 저장하면 컴포넌트가 서버 작업의 결과를 뷰어에 표시할 수 있습니다.
'use client'
import { create } from './actions'
import { useState } from 'react'
export default function Page() {
const [message, setMessage] = useState<string>('')
async function onCreate(formData: FormData) {
const res = await create(formData)
setMessage(res.message)
}
return (
<form action={onCreate}>
<input type="text" name="item" />
<button type="submit">Add</button>
<p>{message}</p>
</form>
)
}
- 알아두면 좋은 점
- 현재 로드 또는 에러 상태를 표시하려면 클라이언트 컴포넌트를 사용해야 합니다. 서버 작업의 안정성이 향상됨에 따라 이러한 값을 검색하는 서버 측 함수에 대한 옵션을 탐색하고 있습니다.
낙관적인 업데이트
- 응답을 기다리지 않고 서버 작업이 완료되기 전에 useOptimistic을 사용하여 UI를 낙관적으로 업데이트합니다.
- 서버에서 응답을 받기 전에 UI를 업데이트!
'use client'
import { experimental_useOptimistic as useOptimistic } from 'react'
import { send } from './actions'
type Message = {
message: string
}
export function Thread({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[]>(
messages,
(state: Message[], newMessage: string) => [
...state,
{ message: newMessage },
]
)
return (
<div>
{optimisticMessages.map((m) => (
<div>{m.message}</div>
))}
<form
action={async (formData: FormData) => {
const message = formData.get('message')
addOptimisticMessage(message)
await send(message)
}}
>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
)
}
쿠기 설정
- 쿠키 기능을 사용하여 서버 작업 내에서 쿠키를 설정할 수 있습니다.
'use server'
import { cookies } from 'next/headers'
export async function create() {
const cart = await createCart()
cookies().set('cartId', cart.id)
}
쿠키 읽기
- 쿠키 기능을 사용하여 서버 작업 내에서 쿠키를 읽을 수 있습니다.
'use server'
import { cookies } from 'next/headers'
export async function read() {
const auth = cookies().get('authorization')?.value
}
쿠키 삭제
- 쿠키 기능을 사용하여 서버 작업 내에서 쿠키를 삭제할 수 있습니다.
'use server'
import { cookies } from 'next/headers'
export async function delete() {
cookies().delete('name')
}
Reference