티스토리 뷰

Next.js 프로젝트에서 가장 흔히 발생하는 버그 중 하나는 서버에서 보내주는 데이터의 구조와 클라이언트에서 기대하는 구조가 일치하지 않을 때 발생합니다. TypeScript를 활용하여 서버와 클라이언트 사이의 '타입 계약(Type Contract)'을 완벽하게 체결하는 방법을 정리했습니다.

1. API Routes: 서버리스 함수의 타입 안전성

Next.js의 API Routes는 NextApiRequest와 NextApiResponse를 통해 강력한 타입 지원을 제공합니다.

1.1. 응답 데이터 구조 정의

먼저 성공 응답과 에러 응답의 구조를 인터페이스로 정의합니다.

// types/api.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

export interface ApiError {
  message: string;
  code?: string;
}

1.2. 핸들러 타입 적용

res.status().json()에 제네릭을 사용하여 전달할 데이터의 타입을 강제할 수 있습니다.

// pages/api/users/[id].ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { User, ApiError } from '@/types/api';

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<User | ApiError> // 성공 또는 에러 타입 지정
) {
  const { id } = req.query;

  if (req.method === 'GET') {
    // DB에서 유저를 찾는 로직 (가정)
    const user = { id: String(id), name: '홍길동', email: 'hong@example.com' };

    if (!user) {
      return res.status(404).json({ message: '유저를 찾을 수 없습니다.' });
    }

    return res.status(200).json(user);
  }

  res.status(405).json({ message: '허용되지 않은 메서드입니다.' });
}

2. SSR (Server-Side Rendering): Props 추론의 기술

getServerSideProps를 사용할 때 가장 큰 실수는 서버 함수에서 반환한 Props의 타입을 컴포넌트에서 수동으로 한 번 더 정의하는 것입니다. 이는 중복이며 실수의 원인이 됩니다.

2.1. GetServerSideProps와 Context 타입

서버 사이드 로직에서는 GetServerSideProps 타입을 사용하여 리턴 타입을 보장합니다.

import { GetServerSideProps } from 'next';

interface Post {
  id: number;
  title: string;
  content: string;
}

export const getServerSideProps: GetServerSideProps<{
  post: Post;
  serverTime: string;
}> = async (context) => {
  const { id } = context.params!; // URL 파라미터 접근
  
  const res = await fetch(`https://api.example.com/posts/${id}`);
  const post: Post = await res.json();

  return {
    props: {
      post,
      serverTime: new Date().toISOString(),
    },
  };
};

2.2. InferGetServerSidePropsType으로 자동 추론

컴포넌트에서는 InferGetServerSidePropsType을 사용하여 서버 함수에서 정의한 props 타입을 그대로 가져옵니다.

import { InferGetServerSidePropsType } from 'next';

// 별도의 Props 인터페이스를 만들 필요가 없습니다!
export default function PostPage({
  post,
  serverTime,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <footer>로드된 시간: {serverTime}</footer>
    </article>
  );
}

3. 고급 기술: 공유 타입 전략 (Shared Types)

프론트엔드와 백엔드가 같은 리포지토리에 있는 Next.js의 특성을 활용하여 타입을 공유하는 것이 핵심입니다.

3.1. DTO (Data Transfer Object) 정의

API 응답 전용 타입을 따로 관리하여 클라이언트와 서버 양측에서 import 하여 사용합니다.

// types/dto.ts
export type CreateUserRequest = {
  name: string;
  email: string;
};

export type UserResponse = {
  id: string;
  createdAt: string;
} & CreateUserRequest;

3.2. 클라이언트 사이드 Fetcher와 연동

useSWR이나 TanStack Query를 사용할 때 API Route에서 정의한 타입을 제네릭으로 넘겨주면 완벽한 자동 완성을 경험할 수 있습니다.

import useSWR from 'swr';
import { UserResponse } from '@/types/dto';

const fetcher = (url: string) => fetch(url).then(res => res.json());

function UserProfile({ userId }: { userId: string }) {
  // 제네릭을 통해 data가 UserResponse 타입임을 보장
  const { data, error } = useSWR<UserResponse>(`/api/users/${userId}`, fetcher);

  if (!data) return <div>로딩 중...</div>;
  return <div>사용자 이름: {data.name}</div>;
}

4. 런타임 검증: Zod와의 결합

TypeScript는 컴파일 타임의 타입만 체크합니다. 실제 API로 들어오는 데이터가 올바른지 확인하려면 Zod와 같은 라이브러리를 API Routes에서 사용하는 것을 권장합니다.

import { z } from 'zod';

const UserSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
});

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    // 런타임에 데이터 구조 검증
    const validatedData = UserSchema.parse(req.body);
    // 검증 성공 시 validatedData는 자동으로 타입이 추론됨
    res.status(200).json({ success: true, data: validatedData });
  } catch (error) {
    res.status(400).json({ message: '잘못된 데이터 형식입니다.' });
  }
}

요약

구분핵심 도구 / 기술효과

API 응답 NextApiResponse<T> 서버에서 응답하는 JSON 구조 강제
SSR Props InferGetServerSidePropsType 서버 데이터와 컴포넌트 Props 타입 동기화
타입 공유 Common Types / DTO 중복 선언 방지 및 API 변경 대응 용이
데이터 검증 Zod / Yup 런타임 환경에서의 타입 안정성 확보

서버에서 클라이언트로 흐르는 모든 데이터에 타입을 입히는 작업은 초기 비용이 들지만, 결과적으로 **"배포 후 에러"**를 90% 이상 줄여주는 최고의 투자입니다.

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
글 보관함