<Form>
<Form>
컴포넌트는 HTML <form>
요소를 확장하여 프리페칭과 로딩 UI, 제출 시 클라이언트 사이드 네비게이션, 그리고 점진적 개선을 제공합니다.
URL 검색 파라미터를 업데이트하는 폼에 유용하며, 위의 기능을 구현하는 데 필요한 보일러플레이트 코드를 줄여줍니다.
기본 사용법:
import Form from "next/form";
export default function Page() {
return (
<Form action="/search">
{/* 제출 시, 입력값이 URL에 추가됩니다.
예: /search?query=abc */}
<input name="query" />
<button type="submit">Submit</button>
</Form>
);
}
import Form from "next/form";
export default function Search() {
return (
<Form action="/search">
{/* 제출 시, 입력값이 URL에 추가됩니다.
예: /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와 클라이언트 사이드 상태를 유지합니다.
- 폼이 보이게 되면 경로를 프리페치합니다. 이는 공유 UI(예:
action
이 함수(서버 액션)인 경우,<Form>
은 React 폼처럼 동작하며, 폼이 제출될 때 액션을 실행합니다.
action
(문자열) Props
action
이 문자열일 때, <Form>
컴포넌트는 다음 props를 지원합니다:
Prop | 예시 | 타입 | 필수 여부 |
---|---|---|---|
action | action="/search" | string (URL 또는 상대 경로) | 예 |
replace | replace={false} | boolean | - |
scroll | scroll={true} | boolean | - |
action
: 폼이 제출될 때 이동할 URL 또는 경로입니다.- 빈 문자열
""
은 업데이트된 검색 파라미터와 함께 동일한 라우트로 이동합니다.
- 빈 문자열
scroll
: 네비게이션 중 스크롤 동작을 제어합니다. 기본값은true
로, 새 라우트의 상단으로 스크롤하며, 뒤로 가기와 앞으로 가기 네비게이션에서는 스크롤 위치를 유지합니다.replace
: 현재 히스토리 상태를 브라우저의 히스토리 스택에 새로 추가하는 대신 대체합니다. 기본값은false
입니다.
action
(함수) Props
action
이 함수일 때, <Form>
컴포넌트는 다음 prop을 지원합니다:
Prop | 예시 | 타입 | 필수 여부 |
---|---|---|---|
action | action={myAction} | function (서버 액션) | 예 |
action
: 폼이 제출될 때 호출될 서버 액션입니다. 자세한 내용은 React 문서를 참조하세요.
알아두면 좋은 점:
action
이 함수일 때는replace
와scroll
props가 무시됩니다.
주의사항
onSubmit
: 폼 제출 로직을 처리하는 데 사용할 수 있습니다. 하지만event.preventDefault()
를 호출하면 지정된 URL로 이동하는 등의<Form>
동작을 재정의합니다.formAction
:<button>
또는<input type="submit">
필드에서action
prop을 재정의하는 데 사용할 수 있습니다. Next.js는 클라이언트 사이드 네비게이션을 수행하지만, 이 접근 방식은 프리페칭을 지원하지 않습니다.basePath
를 사용할 때는formAction
경로에도 포함해야 합니다. 예:formAction="/base-path/search"
.
method
,encType
,target
:<Form>
동작을 재정의하므로 지원되지 않습니다.- 마찬가지로
formMethod
,formEncType
,formTarget
을 사용하여method
,encType
,target
props를 각각 재정의할 수 있지만, 이를 사용하면 네이티브 브라우저 동작으로 폴백됩니다. - 이러한 props를 사용해야 한다면 대신 HTML
<form>
요소를 사용하세요.
- 마찬가지로
key
: 문자열action
에key
prop을 전달하는 것은 지원되지 않습니다. 리렌더링을 트리거하거나 변경을 수행하려면 대신 함수action
을 사용하는 것을 고려하세요.<input type="file">
:action
이 문자열일 때 이 입력 타입을 사용하면 파일 객체 대신 파일 이름을 제출하는 브라우저 동작과 일치합니다.
예시
검색 결과 페이지로 이어지는 검색 폼
경로를 action
으로 전달하여 검색 결과 페이지로 이동하는 검색 폼을 만들 수 있습니다:
import Form from "next/form";
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<button type="submit">Submit</button>
</Form>
);
}
import Form from "next/form";
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<button type="submit">Submit</button>
</Form>
);
}
사용자가 쿼리 입력 필드를 업데이트하고 폼을 제출하면 폼 데이터가 URL의 검색 파라미터로 인코딩됩니다. 예: /search?query=abc
.
알아두면 좋은 점:
action
에 빈 문자열""
을 전달하면 폼은 업데이트된 검색 파라미터와 함께 동일한 라우트로 이동합니다.
결과 페이지에서는 searchParams
page.js
prop을 사용하여 쿼리에 접근하고 외부 소스에서 데이터를 가져오는 데 사용할 수 있습니다.
import { getSearchResults } from "@/lib/search";
export default async function SearchPage({
searchParams,
}: {
searchParams: { [key: string]: string | string[] | undefined };
}) {
const results = await getSearchResults(searchParams.query);
return <div>...</div>;
}
import { getSearchResults } from "@/lib/search";
export default async function SearchPage({ searchParams }) {
const results = await getSearchResults(searchParams.query);
return <div>...</div>;
}
<Form>
이 사용자의 뷰포트에 보이게 되면 /search
페이지의 공유 UI(layout.js
와 loading.js
같은)가 프리페치됩니다. 제출 시, 폼은 즉시 새 라우트로 이동하고 결과를 가져오는 동안 로딩 UI를 표시합니다. loading.js
를 사용하여 폴백 UI를 디자인할 수 있습니다:
export default function Loading() {
return <div>Loading...</div>;
}
export default function Loading() {
return <div>Loading...</div>;
}
공유 UI가 아직 로드되지 않은 경우를 대비하여 useFormStatus
를 사용하여 사용자에게 즉각적인 피드백을 보여줄 수 있습니다.
먼저 폼이 대기 중일 때 로딩 상태를 표시하는 컴포넌트를 만듭니다:
"use client";
import { useFormStatus } from "react-dom";
export default function SearchButton() {
const status = useFormStatus();
return (
<button type="submit">{status.pending ? "Searching..." : "Search"}</button>
);
}
"use client";
import { useFormStatus } from "react-dom";
export default function SearchButton() {
const status = useFormStatus();
return (
<button type="submit">{status.pending ? "Searching..." : "Search"}</button>
);
}
그런 다음 검색 폼 페이지를 업데이트하여 SearchButton
컴포넌트를 사용합니다:
import Form from "next/form";
import { SearchButton } from "@/ui/search-button";
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<SearchButton />
</Form>
);
}
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에 함수를 전달하여 데이터 변경을 수행할 수 있습니다.
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>
);
}
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/navigation
의 redirect
함수를 사용하여 새 게시물 페이지로 이동할 수 있습니다.
알아두면 좋은 점: 폼 제출의 "목적지"는 액션이 실행될 때까지 알 수 없기 때문에,
<Form>
은 공유 UI를 자동으로 프리페치할 수 없습니다.
"use server";
import { redirect } from "next/navigation";
export async function createPost(formData: FormData) {
// 새 게시물 생성
// ...
// 새 게시물로 리다이렉트
redirect(`/posts/${data.id}`);
}
"use server";
import { redirect } from "next/navigation";
export async function createPost(formData) {
// 새 게시물 생성
// ...
// 새 게시물로 리다이렉트
redirect(`/posts/${data.id}`);
}
그런 다음, 새 페이지에서 params
prop을 사용하여 데이터를 가져올 수 있습니다:
import { getPost } from "@/posts/data";
export default async function PostPage({ params }: { params: { id: string } }) {
const data = await getPost(params.id);
return (
<div>
<h1>{data.title}</h1>
{/* ... */}
</div>
);
}
import { getPost } from "@/posts/data";
export default async function PostPage({ params }) {
const data = await getPost(params.id);
return (
<div>
<h1>{data.title}</h1>
{/* ... */}
</div>
);
}
더 많은 예시는 서버 액션 문서를 참조하세요.