콘텐츠로 이동

컴포넌트: Form 컴포넌트

출처 URL: https://nextjs.org/docs/app/api-reference/components/form

API 참조컴포넌트Form 컴포넌트

마지막 업데이트 2026년 2월 20일

<Form> 컴포넌트는 HTML <form> 요소를 확장해 로딩 UI사전 가져오기, 제출 시 클라이언트 측 내비게이션, 점진적 향상을 제공합니다.

URL 검색 매개변수를 업데이트하는 폼에 적합하며, 위 기능을 구현하는 데 필요한 보일러플레이트 코드를 줄여 줍니다.

기본 사용법:

/app/ui/search.tsx

JavaScriptTypeScript

import Form from 'next/form'
export default function Page() {
return (
<Form action="/search">
{/* On submission, the input value will be appended to
the URL, e.g. /search?query=abc */}
<input name="query" />
<button type="submit">Submit</button>
</Form>
)
}

<Form> 컴포넌트의 동작은 action prop에 string을 전달했는지 function을 전달했는지에 따라 달라집니다.

  • action문자열이면 <Form>GET 메서드를 사용하는 네이티브 HTML 폼처럼 동작합니다. 폼 데이터는 검색 매개변수 형태로 URL에 인코딩되고, 제출 시 지정된 URL로 이동합니다. 추가로 Next.js는 다음을 수행합니다.
    • 폼이 화면에 나타나면 경로를 사전 가져오기해 공유 UI(layout.js, loading.js 등)를 미리 로드하여 더 빠른 내비게이션을 제공합니다.
    • 폼 제출 시 전체 페이지 리로드 대신 클라이언트 측 내비게이션을 수행해 공유 UI와 클라이언트 상태를 유지합니다.
  • action함수(Server Action)이면 <Form>은 폼 제출 시 해당 액션을 실행하는 React 폼처럼 동작합니다.

action이 문자열일 때 <Form> 컴포넌트는 다음 프로퍼티를 지원합니다.

Prop예시타입필수
actionaction="/search"string (URL 또는 상대 경로)
replacereplace={false}boolean-
scrollscroll={true}boolean-
prefetchprefetch={true}boolean-
  • action: 폼이 제출될 때 이동할 URL 또는 경로입니다.
    • 빈 문자열 ""을 전달하면 동일한 라우트에서 검색 매개변수만 갱신합니다.
  • replace: 브라우저 히스토리 스택에 새 항목을 추가하지 않고 현재 히스토리 상태를 교체합니다. 기본값은 false입니다.
  • scroll: 내비게이션 중 스크롤 동작을 제어합니다. 기본값은 true이며, 새 라우트의 상단으로 스크롤하고 앞/뒤로 탐색할 때 스크롤 위치를 유지합니다.
  • prefetch: 폼이 사용자 뷰포트에 보여질 때 경로를 사전 가져올지 제어합니다. 기본값은 true입니다.

action이 함수일 때 <Form> 컴포넌트는 다음 프로퍼티를 지원합니다.

Prop예시타입필수
actionaction={myAction}function (Server Action)
  • action: 폼 제출 시 호출되는 Server Action입니다. 자세한 내용은 React 문서를 참고하세요.

알아두면 좋아요: action이 함수일 때는 replacescroll 프로퍼티가 무시됩니다.

  • formAction: <button>이나 <input type="submit"> 필드에서 action prop을 재정의할 수 있습니다. Next.js는 클라이언트 측 내비게이션을 수행하지만, 이 방식은 사전 가져오기를 지원하지 않습니다.

    • basePath를 사용하는 경우 formAction 경로에도 반드시 포함해야 합니다. 예: formAction="/base-path/search".
  • key: 문자열 actionkey prop을 전달하는 것은 지원되지 않습니다. 리렌더링이나 변이를 트리거하려면 함수 action을 사용하는 것이 좋습니다.

  • onSubmit: 폼 제출 로직을 처리할 수 있지만, event.preventDefault()를 호출하면 지정된 URL로 내비게이션하는 <Form> 동작이 덮어씌워집니다.

  • method, encType, target: <Form> 동작을 대체하므로 지원되지 않습니다.

    • 마찬가지로 formMethod, formEncType, formTarget으로 각각 method, encType, target을 재정의할 수 있으며, 이 경우 네이티브 브라우저 동작으로 돌아갑니다.
    • 이 프로퍼티가 꼭 필요하다면 HTML <form> 요소를 사용하세요.
  • <input type="file">: action이 문자열일 때 이 입력 유형을 사용하면 파일 객체 대신 파일 이름을 제출하는 브라우저 동작과 동일하게 작동합니다.

검색 결과 페이지로 이동하는 검색 폼

섹션 제목: “검색 결과 페이지로 이동하는 검색 폼”

경로를 action으로 전달해 검색 결과 페이지로 이동하는 검색 폼을 만들 수 있습니다.

/app/page.tsx

JavaScriptTypeScript

import Form from 'next/form'
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<button type="submit">Submit</button>
</Form>
)
}

사용자가 입력 필드를 수정한 뒤 폼을 제출하면 폼 데이터가 /search?query=abc처럼 검색 매개변수로 URL에 인코딩됩니다.

알아두면 좋아요: action에 빈 문자열 ""을 전달하면 동일한 라우트에서 검색 매개변수만 갱신합니다.

결과 페이지에서는 searchParams page.js prop을 통해 쿼리를 받아 외부 소스에서 데이터를 가져올 수 있습니다.

/app/search/page.tsx

JavaScriptTypeScript

import { getSearchResults } from '@/lib/search'
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
const results = await getSearchResults((await searchParams).query)
return <div>...</div>
}

<Form>이 사용자 뷰포트에 들어오면 /search 페이지의 공유 UI(layout.js, loading.js 등)가 사전 가져오기 됩니다. 제출과 동시에 폼은 새 라우트로 즉시 이동하며 결과를 가져오는 동안 로딩 UI를 표시합니다. loading.js를 사용해 폴백 UI를 설계할 수 있습니다.

/app/search/loading.tsx

JavaScriptTypeScript

export default function Loading() {
return <div>Loading...</div>
}

공유 UI가 아직 로드되지 않은 경우에 대비해 useFormStatus를 사용해 사용자에게 즉각적인 피드백을 제공할 수 있습니다.

먼저, 폼이 대기 중일 때 로딩 상태를 표시하는 컴포넌트를 만듭니다.

/app/ui/search-button.tsx

JavaScriptTypeScript

'use client'
import { useFormStatus } from 'react-dom'
export default function SearchButton() {
const status = useFormStatus()
return (
<button type="submit">{status.pending ? 'Searching...' : 'Search'}</button>
)
}

그런 다음 검색 폼 페이지를 업데이트해 SearchButton 컴포넌트를 사용합니다.

/app/page.tsx

JavaScriptTypeScript

import Form from 'next/form'
import { SearchButton } from '@/ui/search-button'
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<SearchButton />
</Form>
)
}

action prop에 함수를 전달해 변이를 수행할 수 있습니다.

/app/posts/create/page.tsx

JavaScriptTypeScript

import Form from 'next/form'
import { createPost } from '@/posts/actions'
export default function Page() {
return (
<Form action={createPost}>
<input name="title" />
{/* ... */}
<button type="submit">Create Post</button>
</Form>
)
}

변이 후에는 새 리소스로 리디렉션하는 경우가 많습니다. next/navigationredirect 함수를 사용해 새 게시물 페이지로 이동할 수 있습니다.

알아두면 좋아요: 폼 제출의 “목적지”는 액션이 실행되어야만 알 수 있으므로, <Form>은 공유 UI를 자동으로 사전 가져올 수 없습니다.

/app/posts/actions.ts

JavaScriptTypeScript

'use server'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
// Create a new post
// ...
// Redirect to the new post
redirect(`/posts/${data.id}`)
}

새 페이지에서는 params prop을 사용해 데이터를 가져올 수 있습니다.

/app/posts/[id]/page.tsx

JavaScriptTypeScript

import { getPost } from '@/posts/data'
export default async function PostPage({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const data = await getPost(id)
return (
<div>
<h1>{data.title}</h1>
{/* ... */}
</div>
)
}

더 많은 예시는 Server Actions 문서를 참고하세요.