Next.js 라우팅 시 데이터 전달 방법 개요
Next.js에서 페이지 간 이동 시 데이터를 전달하는 방법은 여러 가지가 있다.
이전 글에서 Link 컴포넌트와 useRouter 훅을 사용한 페이지 이동 방법에 대해 알아보았다면, 이번에는 그 과정에서 데이터를 어떻게 전달할 수 있는지 알아보려고 한다.
(보시지 못한 분들은 아래 글을 참고 바란다.)
[Next.js] Next.js + TypeScript에서의 페이지 이동 방법 총정리
Next.js에서의 페이지 이동 방법 개요Next.js에서 페이지 간 이동을 구현하는 방법은 크게 두 가지가 있다.Link 컴포넌트 사용하기useRouter 훅을 사용하여 프로그래매틱하게 라우팅하는 방법일반 React
bbin-guuuu.tistory.com
페이지 간 데이터를 전달하는 주요 방법으로는 크게 네 가지가 있다.
- Query Parameters(쿼리 파라미터)
- Path Parameters(경로 파라미터)
- State 객체
- Context API를 통한 전역 상태 관리
각 방법은 전달하는 데이터의 특성, 보안 요구사항, UX 등에 따라 적합한 상황이 다르다.
하나씩 이해해볼까?
Query Parameters를 이용한 데이터 전달
Query Parameters는 URL의 ? 뒤에 오는 키-값 쌍으로, 페이지 간에 데이터를 전달하는 가장 기본적인 방법 중 하나이다.
이 방법은 GET 요청처럼 데이터가 URL에 노출되어야 하는 경우에 적합하다.
Link 컴포넌트를 사용한 쿼리 파라미터 전달하기
// app/products/page.tsx
import Link from 'next/link';
export default function ProductsPage() {
return (
<div>
<h1>제품 목록</h1>
<div>
<Link href="/products/detail?id=1&category=electronics">
전자제품 1번 상세 보기
</Link>
</div>
<div>
<Link href={{
pathname: '/products/detail',
query: { id: 2, category: 'clothing' },
}}>
의류 2번 상세 보기
</Link>
</div>
</div>
);
}
위 코드에서는 두 가지 방법으로 쿼리 파라미터를 전달하고 있다.
첫 번째는 문자열로 직접 URL을 작성하는 방법이고,
두 번째는 객체 형태로 pathname과 query를 분리하여 전달하는 방법이다.
두 번째 방식이 TypeScript 타입 체킹을 더 잘 지원하므로 복잡한 쿼리 파라미터 구성 시 권장된다!!!
useRouter로 쿼리 파라미터 전달하기
// app/search/page.tsx
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function SearchPage() {
const router = useRouter();
const [searchTerm, setSearchTerm] = useState('');
const [category, setCategory] = useState('all');
const handleSearch = (e: React.FormEvent) => {
e.preventDefault();
// 쿼리 파라미터로 검색 조건 전달
const queryString = new URLSearchParams({
term: searchTerm,
category: category
}).toString();
router.push(`/search/results?${queryString}`);
};
return (
<div>
<h1>제품 검색</h1>
<form onSubmit={handleSearch}>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="검색어 입력"
/>
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
>
<option value="all">전체</option>
<option value="electronics">전자제품</option>
<option value="clothing">의류</option>
</select>
<button type="submit">검색</button>
</form>
</div>
);
}
이 코드에서는 useRouter를 사용하여 프로그래매틱하게 쿼리 파라미터를 구성한다.
URLSearchParams 객체를 사용하면 쿼리 파라미터를 안전하게 구성할 수 있으며, 자동으로 인코딩도 처리해준다.
쿼리 파라미터 접근 및 사용???
Next.js App Router에서는 쿼리 파라미터에 접근하는 방법이 이전 버전과 조금 다르다.
// app/products/detail/page.tsx
'use client';
import { useSearchParams } from 'next/navigation';
export default function ProductDetailPage() {
const searchParams = useSearchParams();
// 쿼리 파라미터 가져오기
const id = searchParams.get('id');
const category = searchParams.get('category');
return (
<div>
<h1>제품 상세 정보</h1>
<p>제품 ID: {id}</p>
<p>카테고리: {category}</p>
</div>
);
}
useSearchParams 훅을 사용하면 현재 URL의 쿼리 파라미터에 쉽게 접근할 수 있다.
이 훅은 SearchParams 객체를 반환하며, 이 객체는 URL의 쿼리 파라미터를 다루기 위한 메서드를 제공한다.
서버 컴포넌트에서는 다음과 같이 쿼리 파라미터에 접근할 수 있다.
// app/products/detail/page.tsx (서버 컴포넌트)
export default function ProductDetailPage({
searchParams,
}: {
searchParams: { [key: string]: string | string[] | undefined };
}) {
const id = searchParams.id;
const category = searchParams.category;
return (
<div>
<h1>제품 상세 정보 (서버 컴포넌트)</h1>
<p>제품 ID: {id}</p>
<p>카테고리: {category}</p>
</div>
);
}
서버 컴포넌트에서는 props로 searchParams 객체를 받아 쿼리 파라미터에 접근할 수 있다.
이 방법을 사용하면 클라이언트에서 자바스크립트를 실행하지 않고도 쿼리 파라미터를 기반으로 페이지를 렌더링할 수 있다.
Path Parameters(동적 라우팅)를 활용한 데이터 전달
Path Parameters는 URL 경로의 일부로 데이터를 전달하는 방식이다.
Next.js에서는 파일 이름이나 폴더 이름을 [parameter] 형태로 지정하여 동적 라우팅을 구현할 수 있다.
아랫글에도 잠시 설명은하지만 다시한번 정리해보고자 한다.
[Next.js] 파일 시스템 기반 라우팅 개념 정리
Next.js 라우팅의 기본 개념Next.js의 가장 큰 특징 중 하나는 파일 시스템 기반 라우팅(File-system based routing)이다.이 방식은 React의 SPA(Single Page Application)에서 흔히 사용되는 React Router와 같은 별도의
bbin-guuuu.tistory.com
동적 라우팅 구성
App Router에서는 폴더 구조를 사용하여 동적 라우팅을 구성한다.
app/
products/
[id]/
page.tsx
이 구조에서 [id]는 동적 세그먼트로, URL의 해당 위치에 어떤 값이든 올 수 있다.
예를 들어, /products/123, /products/456 등의 URL은 모두 같은 페이지 컴포넌트로 라우팅된다.
Link 컴포넌트로 경로 파라미터 전달하기
// app/products/page.tsx
import Link from 'next/link';
export default function ProductsPage() {
const products = [
{ id: 1, name: '무선 이어폰' },
{ id: 2, name: '스마트워치' },
{ id: 3, name: '태블릿' }
];
return (
<div>
<h1>제품 목록</h1>
<ul>
{products.map(product => (
<li key={product.id}>
<Link href={`/products/${product.id}`}>
{product.name}
</Link>
</li>
))}
</ul>
</div>
);
}
각 제품의 ID를 URL 경로의 일부로 포함시켜 해당 제품의 상세 페이지로 이동하게 한다.
이 방식은 RESTful API 설계 원칙과 일치하기 때문에 리소스를 식별하는 데 자주 사용된다.
useRouter로 경로 파라미터 전달하기
// app/admin/page.tsx
'use client';
import { useRouter } from 'next/navigation';
export default function AdminPage() {
const router = useRouter();
const products = [
{ id: 1, name: '무선 이어폰' },
{ id: 2, name: '스마트워치' },
{ id: 3, name: '태블릿' }
];
const handleEdit = (productId: number) => {
router.push(`/products/${productId}/edit`);
};
return (
<div>
<h1>관리자 페이지</h1>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name}
<button onClick={() => handleEdit(product.id)}>
편집하기
</button>
</li>
))}
</ul>
</div>
);
}
useRouter 훅을 사용하여 프로그래매틱하게 동적 경로로 이동하는 방법을 보여준다.
버튼 클릭 이벤트와 같은 사용자 상호작용에 응답하여 페이지 이동을 처리할 때 유용하다.
경로 파라미터 접근 및 사용
// app/products/[id]/page.tsx
export default function ProductDetailPage({
params,
}: {
params: { id: string };
}) {
return (
<div>
<h1>제품 상세 정보</h1>
<p>제품 ID: {params.id}</p>
</div>
);
}
동적 라우팅이 적용된 페이지 컴포넌트는 params 객체를 props로 받는다.
이 객체는 URL 경로에서 추출된 파라미터 값을 포함한다.
위 예제에서는 [id] 폴더에 의해 생성된 id 파라미터에 접근하고 있다.
클라이언트 컴포넌트에서는 useParams 훅을 사용하여 경로 파라미터에 접근할 수 있다. 예시코드를 참고하자.
// app/products/[id]/ClientComponent.tsx
'use client';
import { useParams } from 'next/navigation';
export default function ClientComponent() {
const params = useParams();
const id = params.id;
return (
<div>
<h2>클라이언트 컴포넌트</h2>
<p>제품 ID: {id}</p>
</div>
);
}
State를 활용한 데이터 전달
때로는 URL에 데이터를 노출시키지 않고 페이지 간에 데이터를 전달해야 할 수 있다.
이런 경우 next/navigation의 router.push 메서드를 state 옵션과 함께 사용할 수 있다.
// app/products/client-page.tsx
'use client';
import { useRouter } from 'next/navigation';
export default function ProductsPage() {
const router = useRouter();
const product = {
id: 123,
name: '프리미엄 헤드폰',
price: 299000,
description: '고품질 사운드의 프리미엄 헤드폰',
secret: '비공개 제품 코드: XYZ123'
};
const handleViewDetails = () => {
// state를 사용하여 URL에 노출되지 않는 정보 전달
router.push(`/products/${product.id}`, {
state: { productData: product }
});
};
return (
<div>
<h1>제품 정보</h1>
<button onClick={handleViewDetails}>
상세 정보 보기
</button>
</div>
);
}
🚨 그러나 주의할 점! 🚨
현재 App Router에서는 router.push의 state 옵션이 정식으로 지원되지 않고 있다.
이 코드는 Pages Router에서 작동하는 방식을 보여주는 것으로,
App Router로 마이그레이션할 계획이라면 아래와 같은 대안을 고려해야 한다.
App Router에서의 상태 관리 대안
App Router에서 페이지 간 상태를 공유하기 위한 대안으로는 다음과 같은 방법이 있다.
- URL 쿼리 파라미터 사용 (단, 민감한 정보는 포함하지 않음)
- SessionStorage나 LocalStorage 활용
- Context API 사용
- 상태 관리 라이브러리 사용 (Redux, Zustand 등)
Context API를 활용한 전역 상태 관리
여러 페이지나 컴포넌트에서 공유해야 하는 데이터가 있는 경우, React의 Context API를 활용하여 전역 상태를 관리할 수 있다.
이 경우에는 예시코드들로 살펴보면 좋을 것 같다.
Context 생성
// app/context/ProductContext.tsx
'use client';
import { createContext, useContext, useState, ReactNode } from 'react';
// 타입 정의
interface Product {
id: number;
name: string;
price: number;
description: string;
}
interface ProductContextType {
selectedProduct: Product | null;
setSelectedProduct: (product: Product | null) => void;
}
// Context 생성
const ProductContext = createContext<ProductContextType | undefined>(undefined);
// Provider 컴포넌트
export function ProductProvider({ children }: { children: ReactNode }) {
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
return (
<ProductContext.Provider value={{ selectedProduct, setSelectedProduct }}>
{children}
</ProductContext.Provider>
);
}
// Custom Hook
export function useProductContext() {
const context = useContext(ProductContext);
if (context === undefined) {
throw new Error('useProductContext must be used within a ProductProvider');
}
return context;
}
Context Provider 적용
// app/layout.tsx
import { ProductProvider } from './context/ProductContext';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ko">
<body>
<ProductProvider>
{children}
</ProductProvider>
</body>
</html>
);
}
Context 사용 예시
// app/products/client-page.tsx
'use client';
import { useRouter } from 'next/navigation';
import { useProductContext } from '../context/ProductContext';
export default function ProductsPage() {
const router = useRouter();
const { setSelectedProduct } = useProductContext();
const products = [
{ id: 1, name: '프리미엄 헤드폰', price: 299000, description: '고품질 사운드의 프리미엄 헤드폰' },
{ id: 2, name: '블루투스 스피커', price: 150000, description: '강력한 베이스의 블루투스 스피커' }
];
const handleSelectProduct = (product) => {
// Context에 선택한 제품 정보 저장
setSelectedProduct(product);
// 제품 상세 페이지로 이동
router.push(`/products/${product.id}`);
};
return (
<div>
<h1>제품 목록</h1>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name}
<button onClick={() => handleSelectProduct(product)}>
상세 보기
</button>
</li>
))}
</ul>
</div>
);
}
// app/products/[id]/detail-client.tsx
'use client';
import { useProductContext } from '../../context/ProductContext';
export default function ProductDetailClient() {
const { selectedProduct } = useProductContext();
if (!selectedProduct) return <div>제품 정보를 찾을 수 없습니다.</div>;
return (
<div>
<h1>{selectedProduct.name}</h1>
<p>가격: {selectedProduct.price.toLocaleString()}원</p>
<p>설명: {selectedProduct.description}</p>
</div>
);
}
Context API를 사용하면 전역 상태를 통해 페이지 간에 데이터를 공유할 수 있다.
이 방법은 여러 컴포넌트에서 같은 데이터에 접근해야 하는 경우에 유용하지만,
페이지를 새로고침하면 Context 상태가 초기화된다는 점을 염두에 두어야 한다.
각 데이터 전달 방식의 장단점 살펴보기
Query Parameters
장점:
- URL에 상태가 반영되어 북마크와 공유가 가능
- 서버 컴포넌트에서 직접 접근 가능
- SEO에 유리
단점:
- URL 길이 제한이 있어 대용량 데이터에 적합하지 않음
- 민감한 정보를 포함할 수 없음
- 복잡한 객체나 배열을 전달하기 어려움
Path Parameters
장점:
- RESTful 리소스 식별에 적합
- URL이 간결하고 직관적
- 서버 컴포넌트에서 직접 접근 가능
단점:
- 단순한 값(주로 ID나 슬러그)만 전달 가능
- 여러 값을 전달하기에 적합하지 않음
State 객체 (LocalStorage 등)
장점:
- URL에 데이터가 노출되지 않음
- 복잡한 객체도 전달 가능
- 더 많은 양의 데이터 전달 가능
단점:
- 페이지 새로고침 시 데이터 유지 관리 필요
- 클라이언트 사이드에서만 접근 가능
- 북마크나 공유가 어려움
Context API
장점:
- 컴포넌트 트리 전체에서 데이터 접근 가능
- 복잡한 상태 관리 가능
- 컴포넌트 간 prop drilling 방지
단점:
- 페이지 새로고침 시 상태 초기화
- 설정이 다소 복잡
- 남용 시 성능 이슈 가능성
'Next.js' 카테고리의 다른 글
[Next.js] App Router에서 src 디렉토리 사용할거야??? (2) | 2025.03.22 |
---|---|
[Next.js] Turbopack? 뭐지? 개발 환경 속도를 향상시킨다고?!! (0) | 2025.03.20 |
[Next.js] Next.js + TypeScript에서의 페이지 이동 방법 총정리 (0) | 2025.03.17 |
[Next.js] 'use client'와 React Hooks의 관계 - SSR과 CSR의 경계 이해하기 (0) | 2025.03.16 |
[Next.js] ❗️입문자 주목❗️ TypeScript로 구현하는 로그인 시스템 튜토리얼 (0) | 2025.03.14 |