Next.js에서 router.query를 다룰 때 이런 고민 해보신 적 있나요?
const { type } = router.query; // string | string[] | undefined
// 이렇게 써야 할까요?
const typeString = type.toString();
// 아니면 이렇게?
const typeString = String(type);
실무에서 팀원들마다 다른 방식을 사용하는 걸 보면서 "뭐가 더 좋은 방법일까?" 궁금했던 분들을 위해 준비했습니다.
오늘은 JavaScript의 String()과 toString()의 차이점과 Next.js 환경에서의 안전한 사용법을 알아보겠습니다.
💡 이 글에서 다룰 내용
String()과toString()의 동작 원리 차이- null/undefined 처리에서의 결정적 차이점
- Next.js router.query에서의 실무 적용법
- 타입 안전성을 고려한 베스트 프랙티스
String()은 JavaScript의 전역 함수로, 어떤 값이든 안전하게 문자열로 변환합니다.
// 모든 값을 안전하게 처리
String(123) // "123"
String(true) // "true"
String(null) // "null"
String(undefined) // "undefined"
String([1,2,3]) // "1,2,3"
String({a: 1}) // "[object Object]"
핵심은 null과 undefined도 에러 없이 처리한다는 점입니다.
toString()은 객체의 프로토타입 메서드로, 각 타입별로 다른 구현을 가집니다.
// 일반적인 사용
(123).toString() // "123"
true.toString() // "true"
[1,2,3].toString() // "1,2,3"
// 하지만 이건 에러!
null.toString() // TypeError: Cannot read property 'toString' of null
undefined.toString() // TypeError: Cannot read property 'toString' of undefined
⚠️ 주의사항
toString()은 null이나 undefined에서 호출하면 TypeError가 발생합니다!
Next.js의 router.query는 다음 타입을 가집니다:
query: ParsedUrlQuery // = { [key: string]: string | string[] | undefined }
이때 값이 undefined일 가능성 때문에 안전한 처리가 중요합니다.
import { useRouter } from 'next/router';
const Component = () => {
const router = useRouter();
const { productId } = router.query;
// ❌ 위험: productId가 undefined면 에러
return <h1>{productId.toString()}</h1>;
// ❌ 타입 단언으로 우회하지만 런타임 안전성 없음
return <h1>{(productId as string).toString()}</h1>;
};
const Component = () => {
const router = useRouter();
const { productId } = router.query;
// ✅ String() 사용 - 가장 안전
const safeProductId = String(productId || '');
// ✅ 옵셔널 체이닝 활용
const anotherSafe = productId?.toString() || '';
// ✅ 타입 가드 사용
const getStringValue = (value: string | string[] | undefined): string => {
if (typeof value === 'string') return value;
if (Array.isArray(value)) return value[0] || '';
return '';
};
return <h1>{safeProductId}</h1>;
};
// utils/getSingleQueryParam.ts
export const getSingleQueryParam = (
param: string | string[] | undefined
): string => {
if (typeof param === 'string') return param;
if (Array.isArray(param)) return param[0] || '';
return '';
};
// 사용 예시
const Component = () => {
const router = useRouter();
const productId = getSingleQueryParam(router.query.productId);
const category = getSingleQueryParam(router.query.category);
return (
<div>
<h1>상품: {productId}</h1>
<p>카테고리: {category}</p>
</div>
);
};
const Component = () => {
const router = useRouter();
// ❌ SSR에서 query가 빈 객체일 수 있음
const productId = String(router.query.productId);
// ✅ router가 준비된 후 처리
useEffect(() => {
if (router.isReady) {
const safeProductId = String(router.query.productId || '');
// 안전한 처리...
}
}, [router.isReady, router.query.productId]);
return <div>...</div>;
};
// URL: /products?tags=react&tags=nextjs
const { tags } = router.query; // ["react", "nextjs"]
// ❌ 배열을 고려하지 않은 처리
const tagString = String(tags); // "react,nextjs" (의도한 결과인가?)
// ✅ 배열 처리를 고려한 방법
const getFirstTag = (tags: string | string[] | undefined): string => {
if (Array.isArray(tags)) return tags[0] || '';
return String(tags || '');
};
router.query 처리할 때는 String() 함수 우선 사용router.isReady 체크로 SSR 타이밍 이슈 방지// 이런 간단한 차이가
const bad = (value) => value.toString();
const good = (value) => String(value || '');
// 프로덕션에서는 이런 차이를 만듭니다
// bad: 런타임 에러 → 사용자에게 빈 화면
// good: 안전한 처리 → 정상적인 사용자 경험
태그: #JavaScript #NextJS #TypeScript #React #프론트엔드 #실무팁 #타입안전성 #에러핸들링