Form 컴포넌트 소개
Form
컴포넌트는 HTML의 기본 form
요소를 확장한 거예요. 이걸 사용하면 로딩 UI 미리 불러오기(prefetch), 제출 시 클라이언트 사이드에서 페이지 이동, 그리고 점진적 향상(progressive enhancement) 같은 기능을 쉽게 구현할 수 있답니다.
특히, URL 검색 파라미터(search params)를 업데이트하는 폼에서 유용해요. 기존에 이런 작업 하면서 번거롭게 작성하던 코드량을 훨씬 줄여주거든요.
간단한 기본 사용법은 다음과 같아요:
<Form method="get" action="/search">
<input name="query" />
<button type="submit">Search</button>
</Form>
덧붙일 내용 - 왜 점진적 향상인가?
점진적 향상(Progressive Enhancement)이란 말 그대로 웹 기능을 기본 HTML에서도 사용 가능하면서, 자바스크립트를 지원하는 환경에서는 더 나은 경험을 제공하는 걸 의미해요.
Form
컴포넌트를 쓰면 이렇게 기본 HTML 폼 기능을 유지하면서도, JavaScript가 활성화된 환경에서는 더 빠르고 매끄러운 네비게이션이 가능하죠.
또한, 네트워크 상태가 좋지 않은 경우에도 기본 폼 기능 덕분에 최소한의 작동은 보장되니, 사용자 경험을 꽤나 챙길 수 있어요.
Form
과 비슷한 개념으로는 React의 react-router
라이브러리나 Next.js의 next/link
같은 것들이 있는데, 이 컴포넌트는 좀 더 폼 제출에 특화되어 있다고 보면 됩니다!
필요한 상황에 맞게 잘 활용해 보세요. 폼 제출 시 사용자 경험을 한층 개선해 줄 거예요!
이번 글에서는 Next.js의 Form
컴포넌트 사용법에 대해 다뤄볼게요. 예제를 보면서 설명할게요.
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>
)
}
이 코드가 하는 일?
Form
컴포넌트에 action="/search"
처럼 문자열 형태로 action
을 넘기면, 기본적으로 HTML 폼이 GET 방식으로 데이터를 보내는 방식과 유사해요. 사용자가 입력한 값(여기선 query
input 값)이 URL 쿼리 스트링으로 추가되고, /search?query=입력값
형태로 이동하죠.
근데 여기서 Next.js가 특별한 점은?
특징 | 설명 |
---|---|
미리 불러오기(Prefetch) | 폼이 화면에 나타나면 지정한 경로(/search )를 미리 불러와서 페이지 전환이 훨씬 빨라져요. 이때 레이아웃이나 로딩 컴포넌트 같은 공유 UI도 함께 미리 준비돼요. |
클라이언트 사이드 내비게이션 | 폼을 제출했을 때, 페이지가 전체 새로고침 되지 않고 SPA처럼 클라이언트 사이드에서 경로가 바뀌어요. 덕분에 공유 UI가 유지되고, 유저 인터페이스가 더 부드럽게 느껴집니다. |
참고! action
에 함수 넣으면?
Form
의 action
프로퍼티에 서버 액션(Server Action) 함수를 전달하면, 제출 시 해당 함수가 실행돼요. 완전히 React 방식으로 동작하는 거죠. 서버에서 데이터를 처리하거나 검증하는 등의 작업을 손쉽게 구현할 수 있습니다.
추가 팁!
- 기존 HTML
<form>
과 거의 비슷하게 동작하니까, 폼을 처음 접하는 사람도 금방 적응할 수 있어요. - Next.js는
Form
과 라우팅 시스템을 잘 조합해서 SPA와 SSR의 장점을 살렸답니다! - 만약 POST 방식으로 데이터를 보내고 싶다면,
action
에 서버 함수를 사용하는 게 좋고, 더 복잡한 API 호출도 쉽게 처리 가능해요.
요약하자면, Next.js의 Form
컴포넌트를 이용하면 편하게 폼을 작성하면서도, 빠른 페이지 전환과 클라이언트 상태 유지를 누릴 수 있어요. 상황에 따라 action
에 문자열 URL을 주거나 서버 함수로 처리하는 전략을 골라 쓰시면 됩니다!
action (문자열) Props
Form
컴포넌트에서 action
이 문자열일 때 지원하는 props를 살펴볼게요.
Prop | 예시 | 타입 | 필수 여부 |
---|---|---|---|
action | action="/search" | string (URL 또는 상대 경로) | 필수 |
replace | replace={false} | boolean | 선택 |
scroll | scroll={true} | boolean | 선택 |
prefetch | prefetch={true} | boolean | 선택 |
-
action: 폼이 제출됐을 때 이동할 URL 혹은 경로를 지정해줘요.
만약 빈 문자열""
로 설정하면, 현재 경로에서 검색 파라미터만 업데이트해서 이동해요.
예를 들어, 검색 기능이 있는 페이지에서 검색어만 바뀔 때 유용하겠죠? -
replace: 기본값은
false
예요.
브라우저의 기록(history) 스택에 새 항목을 추가할지 말지 결정하는데요,
만약true
라면 기존 기록을 새 기록으로 교체합니다.
뒤로 가기 버튼을 눌렀을 때 이전 페이지로 돌아가지 않게 하려면 이걸 켜면 돼요. -
scroll: 기본값은
true
이고, 탐색 시 스크롤 동작을 조절해요.
true
면 새 페이지로 이동할 때 스크롤을 맨 위로 올리고, 뒤로/앞으로 가기 시에는 스크롤 위치를 유지해 줘요.
만약 원하는 스크롤 위치가 있다면false
로 설정하고 직접 관리할 수도 있답니다. -
prefetch: 기본값은
true
로,
폼이 화면에 보이면 해당 경로를 미리 불러와서(pre-fetch) 빠르게 전환할 수 있게 도와줘요.
네트워크 트래픽이나 자원 절약을 원한다면 이 옵션을 꺼도 됩니다.
이렇게 각 prop을 잘 활용하면, 단순한 폼 제출 이상의 사용자 경험을 만들 수 있어요.
특히 SPA(싱글 페이지 애플리케이션) 개발 시, 이 props들은 라우팅과 렌더링 방식에 큰 영향을 줍니다.
추가 팁으로, action=""
을 쓰면 URL 파라미터만 바꾸기 때문에
검색 페이지나 필터링 기능 구현 시 유용하니 꼭 기억해 두세요!
action (함수) Props
action
이 함수일 때, Form
컴포넌트는 다음과 같은 prop을 지원합니다:
Prop | 예시 | 타입 | 필수 여부 |
---|---|---|---|
action | action={myAction} | function (서버 액션 함수) | 필수 |
- action: 사용자가 폼을 제출할 때 호출되는 서버 액션 함수입니다. React 문서를 참고하면 더 자세한 정보를 얻을 수 있어요.
여기서 잠깐! action
이 서버 액션 함수라는 점에서 조금 독특한데요. 보통 폼 제출은 클라이언트 사이드에서 처리하거나, API 호출을 통해 이루어지곤 하죠. 그런데 React Server Components 환경에서는 서버에서 직접 실행되는 함수 형태로 액션을 정의할 수도 있답니다. 덕분에 클라이언트와 서버 간 데이터 처리 로직 구분이 깔끔해지고, 성능 최적화에도 도움이 돼요.
혹시 익숙하지 않은분은 "서버 액션"이 뭔지 한 번 찾아보고, React 공식 문서에서 관련 예제를 보면 이해가 금방 될 거예요!
좋은 정보 한 가지! action이 함수일 때는 replace와 scroll 속성들이 무시된다는 점 기억하세요.
주의할 점들
항목 | 설명 |
---|---|
formAction | button 이나 input type="submit" 에서 action 속성을 덮어쓸 수 있어요. Next.js는 클라이언트 사이드 네비게이션을 수행하지만, 이 방법은 prefetch(선불러오기)를 지원하지 않는다는 점 참고하세요. 그리고 basePath를 사용한다면 반드시 formAction 경로에 basePath를 포함시켜야 합니다. 예) formAction="/base-path/search" |
key | 문자열 형태의 action에 key prop을 넘기는 건 지원되지 않습니다. 만약 리렌더링이나 뮤테이션을 트리거하고 싶으면 함수형 action을 사용하세요. |
onSubmit | 폼 제출 로직을 처리할 때 사용 가능하지만, 만약 event.preventDefault() 를 호출하면 Next.js Form 의 기본 동작(즉 URL로 네비게이션)이 무시됩니다. |
method, encType, target | 이 속성들은 Form 의 기본 동작을 오버라이드하기 때문에 지원되지 않아요. 대신에 formMethod , formEncType , formTarget 속성을 써서 각각 method, encType, target을 덮어쓸 수 있긴 하지만 이 경우엔 네이티브 브라우저 동작으로 fallback됩니다. 이들 기능을 꼭 사용해야 한다면 Next.js Form 대신 HTML form 태그를 사용하는 게 좋아요. |
input type="file" | action이 문자열일 때 이 타입의 input은 브라우저 동작과 동일하게 파일 객체 대신 파일명만 제출됩니다. |
사실 Next.js의 Form
컴포넌트를 사용할 때 이런 제약사항들을 미리 알고 있으면 개발할 때 헷갈리는 부분이 줄어요. 특히 prefetch가 안 되거나, 리렌더링 이슈가 생겼을 때는 "혹시 action을 문자열로 쓰고 key를 넘긴 건 아닌가?" 하고 한번 점검해보세요.
더불어 파일 업로드 처리는 Next.js 내장 Form
컴포넌트로 하기엔 제약이 있으니, 이럴 땐 기존 HTML form
태그와 서버 API를 직접 연결하는 방식이 안전합니다.
이런 점들을 잘 기억해두시면 Next.js의 폼 처리 방식을 더욱 능숙하게 사용할 수 있을 거예요!
예제
검색 결과 페이지로 이동하는 검색 폼 만들기
검색 결과 페이지로 이동하는 폼을 만들고 싶다면, action
속성에 결과 페이지 경로를 넣어주면 됩니다. 예를 들어, /search
경로로 이동하도록 설정하면 사용자가 폼을 제출할 때 검색어가 쿼리 파라미터로 붙어서 해당 페이지로 넘어가죠.
import Form from 'next/form'
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<button type="submit">Submit</button>
</Form>
)
}
여기서 중요한 점은 input
에 반드시 name
속성을 주어야 폼 데이터를 제대로 전달할 수 있다는 거예요. name="query"
로 지정한 검색어가 /search?query=검색어
형태로 넘어가게 됩니다.
그리고, Form
컴포넌트는 Next.js의 새로운 폼 처리 방식을 따르는 컴포넌트인데요, 내부적으로 기본 HTML <form>
태그와 유사하게 동작하지만 Next.js 특성에 맞게 최적화되어 있어요. 만약 Next.js 버전에 따라 Form
컴포넌트가 제공되지 않는 경우에는 기본 <form>
태그를 사용하셔도 무방해요.
마지막으로 조금 더 사용자 친화적으로 만들고 싶다면, 입력 필드에 placeholder
를 넣어주거나, 버튼에 좀 더 직관적인 텍스트를 넣는 것도 추천드려요!
<Form action="/search">
<input name="query" placeholder="검색어를 입력하세요" />
<button type="submit">검색</button>
</Form>
이렇게 하면 검색 폼이 더 완성도 있게 보이겠죠? 간단하지만 기본적인 검색 기능을 구현할 때 유용한 방법입니다.
사용자가 검색어 입력 필드에 값을 넣고 폼을 제출하면, 폼 데이터가 URL의 쿼리 파라미터로 인코딩돼서 /search?query=abc
같은 형식으로 이동하게 돼요.
참고로, action 속성에 빈 문자열
""
을 넣으면, 같은 경로로 이동하면서 쿼리 파라미터만 업데이트할 수 있다는 점도 알아두면 좋아요.
검색 결과 페이지에서는 searchParams
라는 page.js
의 prop을 통해 쿼리 값을 받아올 수 있고, 이를 이용해 외부 API에서 데이터를 불러오는 식으로 활용할 수 있답니다.
아래 예제를 살펴볼게요.
import { getSearchResults } from '@/lib/search'
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
// searchParams는 Promise 형태라 await을 해줘야 쿼리 값을 받을 수 있어요
const results = await getSearchResults((await searchParams).query)
return <div>...</div>
}
여기서 한 가지 팁을 드리자면,
searchParams
가 Promise라는 점 때문에 async/await을 꼭 처리해줘야 해요. 깜빡하면 값이 제대로 안 들어와서 당황할 수 있답니다.
정리하자면, 이 방법은 서버사이드 렌더링 환경에서 검색어에 따라 동적인 데이터를 불러오기 아주 편리한 방식이에요. React 컴포넌트에서 직접 URL 쿼리 파라미터를 읽어서 비동기 데이터 요청을 쉽게 처리할 수 있거든요.
만약 사용자가 계속 검색어를 바꾸면서 결과를 보고 싶다면, 이 패턴을 잘 응용하면 굉장히 깔끔한 페이지 구성이 가능하니 꼭 기억해두세요!
Form
이 사용자의 화면에 보이기 시작하면, /search
페이지에 있는 공통 UI(예를 들어 layout.js나 loading.js) 리소스가 미리 불러와집니다(prefetch). 그리고 폼이 제출되면 바로 새로운 경로로 이동하면서, 결과가 로딩되는 동안에는 로딩 UI가 보여집니다. 이렇게 로딩 UI를 설계하려면 loading.js를 사용하면 되는데, 간단한 예시는 다음과 같아요:
export default function Loading() {
return <div>Loading...</div>
}
그런데 여기서 한 가지 더 신경 써야 할 점은, 혹시 공통 UI가 아직 완전히 로드되지 않은 상태에서는 사용자가 답답함을 느낄 수 있다는 거예요. 이런 경우를 대비해서 useFormStatus
훅을 활용해서 즉각적인 피드백을 줄 수 있습니다.
자, 그러면 제출이 진행 중일 때 로딩 상태를 보여주는 컴포넌트를 하나 만들어볼게요. 이 컴포넌트는 사용자가 폼을 제출해서 결과를 기다릴 때 뭔가 진행 중임을 보여줘서 사용자 경험을 더 매끄럽게 만들어줍니다.
'use client'
import { useFormStatus } from 'react-dom'
export default function FormProgress() {
const { pending } = useFormStatus()
return (
<div>
{pending ? '폼 제출 중...' : null}
</div>
)
}
여기서 useFormStatus
는 React Server Components 환경에서 폼 제출 상태(예: 대기중인지 여부)를 추적할 수 있게 도와주는 훅이에요. pending
이 true
일 때는 폼이 제출 중임을 의미하죠.
추가 팁 - 이런 경우에 쓰면 좋아요!
- 사용자가 버튼을 여러 번 반복해서 누르는 걸 방지하고 싶을 때
- 네트워크가 느려서 서버 응답 시간이 긴 경우 진행 상태를 명확히 보여주고 싶을 때
- 전체 페이지가 아닌 일부 컴포넌트에만 로딩 표시를 제한하고 싶을 때
요약
Form
이 화면에 보이면/search
페이지의 공통 UI 리소스를 미리 받아서 빠르게 렌더링 가능- 폼 제출 시 로딩 UI(loading.js)로 사용자 피드백 제공
useFormStatus
훅을 이용해 폼 제출이 진행 중인 상태를 감지하고 즉각적으로 사용자에게 알려줄 수 있음
이렇게 하면 사용자 경험이 훨씬 부드러워지고, 로딩중임을 확실히 인지시켜줘서 UX가 좋아지겠죠? 다음에도 이런 React Server Components 관련 내용 자주 알려드릴게요!
이번에는 React(특히 Next.js)에서 서버 액션(Server Actions)을 활용한 폼과 버튼 구현 예제를 함께 살펴볼게요.
먼저, SearchButton
컴포넌트를 보시면, useFormStatus
훅을 활용해서 폼이 제출 중인지 아닌지 상태를 확인하고 있죠.
'use client'
import { useFormStatus } from 'react-dom'
export default function SearchButton() {
const status = useFormStatus()
return (
<button type="submit">{status.pending ? 'Searching...' : 'Search'}</button>
)
}
useFormStatus
는 폼 제출 상태를 받아와서, 제출이 진행 중일 때는 버튼 텍스트를 'Searching...'으로, 그렇지 않을 때는 'Search'로 바꾸어 줘요.- 그리고
use client
지시자는 이 컴포넌트가 클라이언트 컴포넌트임을 Next.js에 알려줍니다. 서버 컴포넌트와 클라이언트 컴포넌트를 적절히 분리해서 사용하는 것이 Next.js 13에서 중요한 포인트입니다.
그 다음, 실제 검색 폼 페이지 코드를 보면 이렇게 돼있어요.
import Form from 'next/form'
import { SearchButton } from '@/ui/search-button'
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<SearchButton />
</Form>
)
}
- 여기서는
next/form
라이브러리에서 가져온Form
컴포넌트를 사용하고 있네요. Form
컴포넌트에action
으로/search
를 넘겨서, 제출하면 해당 서버 액션이 실행될 거예요.- 그리고
SearchButton
을 넣어서 폼 제출 시 상태가 표시되도록 했죠.
Server Actions란?
Next.js 13부터 도입된 서버 액션 기능은 클라이언트에서 서버 함수를 직접 호출하듯 코드를 작성할 수 있게 해 줍니다. 기존에는 API 라우트로 POST 요청 보내고 응답 받는 과정이 필요했다면, 서버 액션을 사용하면 그 과정을 훨씬 간편하게 만들 수 있죠.
이번 예제의 핵심은:
- 클라이언트에서 폼 제출 시
Form
컴포넌트가 서버 액션을 호출한다. - 서버 액션 실행 중일 때 버튼 상태(
Searching...
)를 통해 사용자가 인지할 수 있다.
더 알아두면 좋은 팁들!
내용 | 설명 |
---|---|
useFormStatus | 폼 제출 상태를 알 수 있어 UI에 반영하기 좋아요. |
클라이언트 컴포넌트 | 상태를 관리할 버튼 등 인터랙티브한 부분은 클라이언트 컴포넌트로 작성해야 해요. |
Form 컴포넌트 | 기존 폼 태그를 대체하면서 서버 액션과 자연스럽게 연동됩니다. |
서버 액션 개발 | 서버에서 실행되는 함수이므로 보안이 중요한 로직을 담기 좋아요. |
마치며
이번에 소개한 useFormStatus
와 Form
컴포넌트를 활용하면 서버 액션과 클라이언트 상태를 아주 깔끔하게 관리할 수 있어요. Next.js 13의 서버 액션 기능은 단순한 API 통신보다 훨씬 직관적이고 강력한 도구니 꼭 한번 적용해 보고, 더 넓은 가능성을 찾아보길 추천해요!
필요하면 서버 액션 만드는 방법과 연동하는 예제도 함께 공유할 테니, 관심 있으면 알려 주세요 :)
이번에 Next.js에서 Form
태그를 활용하는 방법을 살펴봤어요. 그냥 <form>
태그 대신 Form
컴포넌트를 쓰면, mutation(데이터 변경 작업)을 할 때 간편하게 액션 함수를 넘겨줄 수 있답니다.
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>
)
}
여기서 createPost
는 서버 사이드에서 실행되는 함수로, 폼에 제출된 데이터를 받아 글을 생성하는 역할을 하죠. 이렇게 함수 형태로 액션을 넘기면, 폼 제출 시 자동으로 해당 함수가 호출됩니다.
그리고 글이 만들어지고 나서 보통 새 글 페이지로 리다이렉트 하고 싶은 경우가 많잖아요? 이럴 땐 Next.js의 next/navigation
모듈에 있는 redirect
함수를 사용하면 돼요.
import { redirect } from 'next/navigation';
export async function createPost(formData) {
const post = await createPostInDB(formData);
redirect(`/posts/${post.id}`);
}
여기서 주의할 점은요, 폼이 제출된 후에 액션 함수가 실행되기 때문에, 이 때까지 어디로 리다이렉트할지 모릅니다. 그래서 Form
컴포넌트는 자동으로 어떤 UI를 미리 불러오거나(prefetch) 준비하는 기능이 없어요. 즉, 네비게이션이 동적인 상황이라 미리 준비하지 않는다는 뜻입니다.
참고로 알아두면 좋은 점
Form
컴포넌트를 사용하면 서버 액션을 쉽고 깔끔하게 연결할 수 있어요.- 꼭
redirect
를 사용해서 리다이렉션을 처리해야 하고, 클라이언트에서 직접 네비게이션을 하지 않는 게 좋습니다. - 서버 사이드 액션 함수 안에서는 보통 데이터베이스 작업이나 외부 API 호출 같은 일을 처리합니다.
- 사용자가 제출한 데이터는
formData
형태로 받게 되며, 이걸 활용해 원하는 작업을 하면 됩니다.
Next.js 13 초기 버전과 비교하면 정말 좋아진 점 중 하나가 이런 서버 액션과 클라이언트가 자연스럽게 연동된다는 거예요. 개발할 때 훨씬 깔끔하고 안정적으로 만들어서 추천해요!
요즘 Next.js에서 서버 액션(Server Actions)을 활용하는 방법을 공부하고 있는데, 간단하게 정리해볼게요. 서버 액션을 쓰면 서버에서 데이터를 처리하고 그 결과에 따라 바로 리다이렉트도 쉽게 할 수 있어서 편리하답니다.
우선, 새 글을 만드는 createPost
서버 액션 함수부터 볼게요:
'use server'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
// 새로운 글 생성 로직이 여기 들어가겠죠?
// 예를 들어 DB에 저장하거나 API를 호출하고...
// 새 글의 ID를 받아왔다고 가정
redirect(`/posts/${data.id}`) // 생성 완료 후 새 글 페이지로 이동
}
여기서 'use server' 지시어는 이 함수가 서버에서만 실행된다는 뜻이고, redirect
함수로 새로운 글 페이지로 이동할 수 있어요. 클라이언트에서 별도의 처리 없이 서버에서 바로 리다이렉트되는 점이 신기하죠?
그리고 새 글 페이지 컴포넌트에서는 params
를 받아서 해당 글 데이터를 불러옵니다:
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>
)
}
여기서 params
가 프로미스로 되어있는 게 좀 특이한데, 이게 Next.js의 새로운 동적 라우팅 방식 때문이에요. 비동기로 params를 받아서 바로 데이터를 fetch할 수 있답니다.
덧붙여서, Server Actions은 Next.js 13에서 소개된 기능이고, 기존에 API 라우트를 따로 만들지 않아도 서버 코드를 컴포넌트 안에서 선언해서 사용할 수 있어 개발 생산성을 높여줘요.
더 다양한 예제와 활용 팁은 서버 액션 공식 문서에서 확인해보세요! 한번 익숙해지면 클라이언트-서버 데이터 흐름이 훨씬 깔끔해져서 추천합니다 :)