Redux Toolkit(RTK)은 Redux 공식팀에서 제공하는 상태관리 툴킷으로, 개발자들이 겪는 복잡한 설정과 보일러플레이트 문제를 해결해줍니다. 특히 현업에서 많이 쓰이는 기능을 잘 알고 있으면, 개발 생산성과 유지보수성이 모두 크게 향상됩니다.

이 글에서는 RTK에서 가장 자주 사용되는 기능 5가지를 실전 코드와 함께 자세히 소개합니다.


1. createSlice – 상태와 리듀서를 하나로 통합

createSlice는 RTK의 핵심 기능 중 하나로, 상태 초기값, 액션 생성, 리듀서를 하나의 선언형 코드로 관리할 수 있게 해줍니다. Redux의 대표적인 단점인 ‘액션 따로, 리듀서 따로’ 구조를 없애줍니다.

예시 코드

import { createSlice } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1 },
    decrement: (state) => { state.value -= 1 },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    }
  }
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

state.value += 1처럼 불변성 걱정 없이 코드를 작성할 수 있는 건 RTK 내부에 Immer가 기본 내장되어 있기 때문입니다.


2. createAsyncThunk – 비동기 요청과 상태를 동시에 관리

기존 Redux에서 비동기 요청을 다루기 위해선 Redux-thunk 또는 Redux-saga 등을 별도로 설정해야 했습니다.
하지만 RTK는 createAsyncThunk를 통해 비동기 API 호출과 상태 관리를 하나의 흐름으로 처리할 수 있습니다.

예시 코드

import { createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'

export const fetchUser = createAsyncThunk('user/fetchUser', async (userId) => {
  const response = await axios.get(`/api/users/${userId}`)
  return response.data
})

createAsyncThunk로 만들어진 액션은 자동으로 3단계 상태를 가집니다:

  • pending: 요청 중
  • fulfilled: 요청 성공
  • rejected: 요청 실패

이를 extraReducers에서 다음과 같이 처리합니다:

extraReducers: (builder) => {
  builder
    .addCase(fetchUser.pending, (state) => {
      state.status = 'loading'
    })
    .addCase(fetchUser.fulfilled, (state, action) => {
      state.status = 'succeeded'
      state.user = action.payload
    })
    .addCase(fetchUser.rejected, (state, action) => {
      state.status = 'failed'
      state.error = action.error.message
    })
}

 

3. createEntityAdapter – 리스트 상태를 효율적으로 관리

여러 개의 데이터를 리스트 형태로 관리할 때는 createEntityAdapter가 매우 유용합니다. 예를 들어, 사용자 목록, 게시판 글 리스트 등에서 다음과 같은 기능이 필요합니다:

  • 데이터 정렬
  • id 기반 조회
  • 빠른 추가/삭제/수정

기능 예시

import { createEntityAdapter } from '@reduxjs/toolkit'

const usersAdapter = createEntityAdapter({
  selectId: (user) => user.id,
  sortComparer: (a, b) => a.name.localeCompare(b.name)
})

const initialState = usersAdapter.getInitialState({
  loading: false
})

usersAdapter는 다음과 같은 메서드를 제공합니다:

  • addOne, addMany, removeOne, updateOne
  • selectAll, selectById 등 selector도 자동 생성

복잡한 리스트를 단순한 객체 조작처럼 다룰 수 있어, 실무에서 매우 자주 사용됩니다.


4. RTK Query (createApi) – 서버 상태를 자동으로 관리

RTK Query는 서버에서 데이터를 가져오고, 캐시하고, 리패치하고, 무효화하는 과정을 자동화해주는 기능입니다.
React Query와 유사하지만 Redux 상태와 자연스럽게 통합됩니다.

기능 요약

  • API 요청을 캐시하고 자동으로 재요청
  • useQuery, useMutation 훅 제공
  • 요청 상태를 자동 관리 (isLoading, isError, data 등)
  • 자동으로 re-fetch, cache invalidation 가능

예시 코드

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const userApi = createApi({
  reducerPath: 'userApi',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: (builder) => ({
    getUser: builder.query({
      query: (id) => `/users/${id}`
    })
  })
})

export const { useGetUserQuery } = userApi

이 기능만으로도 대부분의 서버 통신 로직을 간단하게 관리할 수 있습니다.

 

Redux Toolkit(RTK)는 단순한 상태 저장소 이상의 기능을 제공하여, 비동기 요청의 상태 변화를 효율적으로 관리할 수 있습니다.
이 글에서는 RTK의 핵심 기능인 반응형 상태 처리 개념부터 Axios와의 차이점, 그리고 실전 코드 예제까지 한 번에 정리해드립니다.


RTK의 실시간 상태 처리란?

RTK는 다음 세 가지 상태를 자동으로 관리해 줍니다:

  • pending: 요청 중
  • fulfilled: 요청 성공
  • rejected: 요청 실패

이 상태를 기반으로 로딩 스피너, 에러 메시지, UI 갱신 등을 자동화할 수 있어, 아래와 같은 활용이 가능합니다.

 

활용 예시

  1. 채팅 메시지 수신: WebSocket 또는 polling으로 서버에서 메시지를 받아 상태 갱신
  2. 주문 상태 추적: 배송 상태에 따라 UI를 동적으로 변경
  3. 파일 업로드 진행률: 업로드 상태 및 진행률 표시
  4. 알림 센터 구현: 미확인 알림 상태 추적 및 UI 반영

Axios와 RTK 비교

항목 Axios RTK
목적 HTTP 요청 처리 상태관리 + 비동기 통합
상태관리 직접 구현해야 함 자동 제공
로딩/에러 처리 수동 처리 내장 상태로 가능
중앙 상태 공유 Redux 등 필요 Redux store 통합
코드 구조 자유도 높음 공식 구조 권장
DevTools 직접 설정 필요 자동 통합
 

Axios는 네트워크 요청만 처리하고 상태는 별도 관리가 필요합니다.
반면 RTK는 요청과 상태, 흐름을 Redux 아키텍처 안에서 일관되게 처리할 수 있다는 점이 큰 장점입니다.


RTK 사용법 (설정 및 코드 예시)

1. 패키지 설치

npm install @reduxjs/toolkit react-redux

 

2. Slice 생성

import { createSlice } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1 },
    decrement: (state) => { state.value -= 1 }
  }
})

export const { increment, decrement } = counterSlice.actions
export default counterSlice.reducer
 

3. 비동기 Thunk 생성

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import axios from 'axios'

export const fetchUser = createAsyncThunk('user/fetch', async (userId) => {
  const res = await axios.get(`/api/user/${userId}`)
  return res.data
})

const userSlice = createSlice({
  name: 'user',
  initialState: { data: null, status: 'idle', error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => { state.status = 'loading' })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.status = 'succeeded'
        state.data = action.payload
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.status = 'failed'
        state.error = action.error.message
      })
  }
})

export default userSlice.reducer
 

4. Store 설정

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counterSlice'
import userReducer from '../features/userSlice'

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    user: userReducer
  }
})

5. Provider 설정

 
import { Provider } from 'react-redux'
import { store } from './app/store'

<Provider store={store}>
  <App />
</Provider>

Next.js 13 이상부터 도입된 app 디렉토리 기반 구조는 서버 컴포넌트(Server Component)를 기본으로 채택합니다. 이로 인해 클라이언트 전용 기능인 useState, useEffect, onClick 등을 사용할 경우, 아래와 같은 오류가 발생할 수 있습니다.


에러 메시지 예시

Error: You're importing a component that needs `useState`. This React hook only works in a client component. To fix, mark the file (or its parent) with the "use client" directive.

원인 설명

Next.js의 app 디렉토리에서는 페이지나 컴포넌트 파일이 기본적으로 서버 컴포넌트로 처리됩니다. 서버 컴포넌트는 브라우저 상호작용이 필요 없는 정적/비동기 작업에 적합하지만, useState처럼 브라우저에서만 동작해야 하는 React Hook은 클라이언트 컴포넌트에서만 사용 가능합니다.


해결 방법: "use client" 지시어 추가

해당 컴포넌트 파일 최상단에 다음 줄을 추가하면 해결됩니다:

"use client";

예시 코드

"use client";

import { useState } from "react";
import { Input } from "@/components/ui/input";

export default function SignUpPage() {
  const [agree, setAgree] = useState(false);

  return (
    <div>
      <Input type="checkbox" checked={agree} onChange={() => setAgree(!agree)} />
      <label>동의합니다</label>
    </div>
  );
}

언제 "use client"를 붙여야 할까?

다음과 같은 경우, 해당 파일(혹은 그 상위 레벨 컴포넌트)은 클라이언트 컴포넌트여야 하므로 "use client"를 선언해야 합니다:

  • useState, useEffect, useRef 등 React 훅을 사용하는 경우
  • onClick, onChange 등 이벤트 핸들러를 사용하는 경우
  • 브라우저 전용 API(window, localStorage 등)를 사용하는 경우

참고 사항

  • "use client"는 해당 파일에만 적용됩니다. 하위 컴포넌트에는 자동으로 전파되지 않습니다.
  • 가능하면 클라이언트 기능은 최소한의 파일에서만 분리해 사용하는 것이 SSR 최적화에 유리합니다.

정리

Next.js의 서버 중심 아키텍처는 성능과 최적화에 강점이 있지만, 사용자 인터랙션을 구현하려면 클라이언트 컴포넌트가 필요합니다. "use client" 지시어는 이를 선언하는 방식이며, 이 구조를 이해하면 더욱 효율적인 Next.js 프로젝트 구성이 가능합니다.

Next.js는 파일 기반 라우팅(file-based routing) 시스템을 채택하고 있습니다. 이는 기존의 React Router처럼 별도 설정 없이, 폴더 구조만으로 자동으로 라우팅이 구성되는 방식입니다.

1. 기본 라우팅

src/app/page.jsx → /
src/app/about/page.jsx → /about
src/app/login/page.jsx → /login

각 디렉토리 안에 있는 page.jsx 파일이 해당 URL 경로와 매핑됩니다.

2. 동적 라우팅

Next.js에서는 대괄호([param]) 문법으로 동적 라우팅이 가능합니다.

src/app/posts/[id]/page.jsx → /posts/1, /posts/abc 등 다양한 id 값 허용

3. 중첩 라우트

디렉토리를 중첩시켜 라우트를 구성할 수 있습니다. 각 디렉토리에서 layout.jsx를 정의하면 해당 경로 이하의 모든 하위 페이지에 공통 레이아웃을 적용할 수 있습니다.

4. API 라우트

src/app/api/hello/route.js → /api/hello

Next.js는 API 라우팅을 위한 구조도 제공합니다. 간단한 백엔드 기능을 이 구조로 함께 개발할 수 있습니다.

5. 기타 관련 파일들

  • layout.jsx: 공통 레이아웃 정의
  • loading.jsx: 로딩 중 화면 정의
  • error.jsx: 에러 발생 시 렌더링할 컴포넌트
  • head.jsx: <head> 메타 태그 커스터마이징

결론

Next.js의 라우팅은 매우 직관적이며 유지보수에 강합니다. 코드를 분리하고 관리하기 좋으며, 디렉토리 구조만으로도 복잡한 웹페이지의 구성과 흐름을 명확하게 표현할 수 있습니다. 이는 규모가 커질수록 더욱 진가를 발휘합니다.

 

웹 프론트엔드 개발을 시작할 때 수많은 프레임워크와 라이브러리 중 어떤 것을 선택할지 고민하게 됩니다. React, Vue, Angular, Svelte 등 다양한 선택지가 있지만, 최근에는 Next.js가 압도적으로 주목받고 있습니다. 왜 그런지, 그리고 왜 나 역시 Next.js를 선택했는지를 공유하고자 합니다.

✅ React 위에서 동작하는 프레임워크

Next.js는 React를 기반으로 만들어졌기 때문에 기존에 React를 알고 있다면 진입 장벽이 낮습니다. 순수 React만으로 프로젝트를 구성하면 라우팅, SSR(서버사이드 렌더링), SEO 최적화 등을 직접 처리해야 합니다. 반면, Next.js는 이런 부분을 프레임워크 수준에서 기본 제공합니다.

✅ 서버 사이드 렌더링(SSR) & 정적 생성(SSG) 지원

SEO에 민감한 웹페이지(예: 블로그, 쇼핑몰 등)에서는 SSR이 매우 중요합니다. Next.js는 getServerSideProps, getStaticProps 등을 통해 페이지를 서버에서 렌더링하거나 빌드시 미리 생성할 수 있게 해줍니다. 이는 React가 SPA(Single Page Application)로서 갖는 SEO 한계를 극복합니다.

✅ 파일 기반 라우팅 + API 라우트

Next.js는 디렉토리 구조만으로 라우팅을 구성할 수 있어 직관적이며 유지보수가 쉽습니다. 또, /api 디렉토리를 통해 API 라우트를 구성할 수 있어 간단한 백엔드 기능까지 포함한 풀스택 개발이 가능합니다.

✅ Fullstack에 가까운 생산성

Next.js는 Vercel이라는 호스팅 플랫폼과도 잘 통합되어 있으며, 프론트엔드와 백엔드를 함께 관리할 수 있는 Fullstack 개발 환경을 지향합니다. Rust, Go, Express 등의 백엔드 언어나 프레임워크도 뛰어나지만, Next.js는 프론트 중심의 프로젝트를 빠르게 진행할 수 있게 해줍니다.

✅ TypeScript, ESLint, Tailwind 등과의 높은 호환성

Next.js는 초기 설정만으로도 TypeScript, ESLint, Prettier, Tailwind CSS 등의 툴을 자동으로 통합할 수 있어, 현대적 개발 환경을 구축하는 데 매우 유리합니다.

결론

React를 잘 알고 있다면, 그리고 빠르게 프론트엔드와 SSR을 적용한 웹페이지를 만들고 싶다면 Next.js는 매우 매력적인 선택지입니다. Rust나 다른 백엔드 프레임워크가 뛰어난 퍼포먼스를 제공할 수는 있지만, 웹의 빠른 개발과 배포, 사용자 경험을 생각하면 Next.js가 가진 강점은 분명합니다.

Next.js 프로젝트에서 다음과 같은 오류를 만나는 경우가 있습니다:

Module not found: Can't resolve 'lucide-react'

이 오류는 외부 패키지인 lucide-react가 프로젝트에 설치되지 않았을 때 발생합니다. 이 글에서는 오류의 원인과 해결 방법을 간단하게 정리해보겠습니다.


1. 오류 원인

lucide-react는 아이콘 컴포넌트를 제공하는 라이브러리로, 보통 shadcn/ui 기반의 UI 컴포넌트를 생성할 때 함께 사용됩니다. 예를 들어 select, button, input 등 컴포넌트 내에서 다음과 같이 아이콘을 import할 수 있습니다:

import { CheckIcon, ChevronDownIcon } from "lucide-react";

하지만 lucide-react 패키지가 설치되어 있지 않으면, Next.js는 해당 모듈을 찾을 수 없다는 에러를 출력하게 됩니다.


2. lucide-react란?

lucide-react는 Feather Icons를 기반으로 만든 React용 오픈소스 아이콘 라이브러리입니다. SVG 아이콘을 컴포넌트 형태로 제공하며, 다음과 같은 특징을 가집니다:

  • React 컴포넌트로 손쉽게 아이콘 사용 가능
  • Feather 스타일의 미니멀하고 직관적인 디자인
  • shadcn/ui에서 기본 아이콘 세트로 자주 사용됨
  • 필요에 따라 개별 아이콘만 import하여 번들 크기 최적화 가능

사용 예시:

import { CheckIcon, TrashIcon } from 'lucide-react';

function Example() {
  return <CheckIcon size={24} className="text-green-500" />;
}

공식 GitHub: https://github.com/lucide-icons/lucide


3. 해결 방법

아래 명령어를 실행하여 lucide-react 패키지를 설치하면 문제가 해결됩니다.

npm install lucide-react

또는 yarn을 사용하는 경우:

yarn add lucide-react

설치가 완료되면 개발 서버를 재시작하세요:

npm run dev

4. 추가 팁: Shadcn UI 사용자라면

shadcn/ui를 이용해 컴포넌트를 생성한 경우, 필요한 의존성(lucide-react, tailwind-variants 등)이 자동으로 설치되지 않는 경우도 있습니다. 따라서 컴포넌트 생성 후 README.md 또는 CLI 안내에 따라 필요한 의존성은 수동으로 설치해야 합니다.

Next.js로 프로젝트를 개발하다 보면 아래와 같은 오류를 만날 수 있습니다:

Module not found: Can't resolve '@/components/ui/tabs'

이 오류는 프로젝트가 @/components/ui/tabs 경로에 해당하는 파일이나 모듈을 찾을 수 없을 때 발생합니다. 이번 글에서는 이 문제를 해결하는 방법을 단계적으로 정리해보겠습니다.


1. 경로에 파일이 실제로 존재하는지 확인

가장 먼저 확인할 것은 tabs.tsx 파일이 실제로 존재하는지 여부입니다.

경로 예시:

src/
└── components/
    └── ui/
        └── tabs.tsx

혹은 tabs/index.tsx 형식으로 되어 있을 수도 있습니다. 파일이 존재하지 않으면 직접 생성하거나, 다음 단계로 넘어가세요.


2. @ alias가 작동하도록 설정되었는지 확인 (tsconfig.json)

@를 사용하는 경우, 해당 경로를 tsconfig.json 또는 jsconfig.json에서 설정해야 합니다.

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

이 설정이 없다면 @/components/...는 정상적으로 인식되지 않습니다. 설정을 저장한 후, 에디터와 서버를 재시작해야 적용됩니다.


3. Shadcn UI를 사용하는 경우

shadcn/ui 컴포넌트 라이브러리를 사용하는 프로젝트라면, 해당 컴포넌트를 명령어로 추가해야 합니다.

npx shadcn-ui@latest add tabs

위 명령어를 실행하면 components/ui/tabs.tsx가 생성되어 오류가 해결됩니다.


4. 캐시 삭제 후 재빌드

가끔 Next.js의 캐시로 인해 오류가 지속되는 경우가 있습니다. 다음 명령어로 .next 캐시 폴더를 삭제하고 다시 실행해보세요.

rm -rf .next
npm run dev

5. 기타 체크리스트

  • Git에 누락된 파일이 있는지 확인
  • 파일명이 대소문자를 정확히 구분하는지 확인 (Tabs.tsx vs tabs.tsx)
  • ESLint, TypeScript 에러가 있는지 확인

프론트엔드를 다루는 것이 여전히 익숙하지 않은 백엔드 개발자에게, UI 구성은 때때로 가장 버거운 영역이 되곤 합니다. 저 또한 그랬습니다. 하지만 최근 shadcn/ui라는 도구를 접하고, 프론트 작업에 대한 부담을 상당히 덜 수 있었습니다.

shadcn/ui란?

shadcn/ui는 React 기반 프로젝트에서 사용할 수 있는 복사-붙여넣기 방식의 UI 컴포넌트 모음입니다. npm 패키지를 설치해서 쓰는 방식이 아니라, 원하는 컴포넌트의 소스 코드를 CLI 도구로 직접 프로젝트에 추가하는 방식입니다. 이 점이 기존 UI 라이브러리들과 가장 큰 차이점이자 장점입니다.

기본적으로 Tailwind CSSRadix UI 기반으로 구성되어 있어, 시멘틱하고 접근성이 뛰어난 UI 컴포넌트를 만들 수 있습니다. 커스터마이징이 쉬우면서도 일관된 디자인 시스템을 구축할 수 있죠.

왜 백엔드 개발자에게 유용한가?

1. 디자인 고민이 줄어듬

Tailwind로 스타일이 잘 정리되어 있고, 기본 구성 자체가 감각적으로 디자인되어 있어 별도의 UI 감각 없이도 '그럴듯한' 화면을 만들 수 있습니다.

2. 커스터마이징의 유연함

소스코드를 직접 프로젝트에 포함시키는 방식이기 때문에, 필요에 따라 로직이나 UI 구조를 자유롭게 수정할 수 있습니다. "라이브러리에서 막혀서 못 바꾸는 문제"가 발생하지 않습니다.

3. 접근성과 반응형 지원

Radix UI를 기반으로 하기 때문에 접근성(Accessibility)을 기본으로 갖추고 있고, Tailwind를 통한 반응형 디자인도 손쉽게 적용 가능합니다.

실제 사용 방식

프로젝트 초기화

npx shadcn-ui@latest init

컴포넌트 추가

npx shadcn-ui@latest add button

컴포넌트 사용 예시 (React)

import { Button } from "@/components/ui/button"

export default function Example() {
  return <Button>Click me</Button>
}

기존 디자인 시스템과의 유사점

예전에는 회사나 팀 단위로 자체 디자인 시스템을 만들고, 여러 프로젝트에서 같은 컴포넌트를 복사해서 쓰는 일이 많았습니다. shadcn/ui는 이런 방식을 현대적으로 정제한 도구입니다. 단순한 복붙이 아닌, 공식적으로 커스터마이징 가능한 템플릿 소스코드를 CLI로 불러와 쓰는 구조라고 보면 됩니다.

추천 가이드 및 문서 링크

결론

디자인과 프론트엔드 UI 구현이 부담스러운 백엔드 개발자에게 shadcn/ui는 꽤 괜찮은 해결책이 될 수 있습니다. 복잡한 스타일링이나 컴포넌트 설계에 시간을 들이지 않고도 일관된 디자인의 UI를 구성할 수 있으며, 필요에 따라 확장성 있는 코드로 자연스럽게 이어갈 수 있다는 점이 매우 매력적입니다.

React + Tailwind + Radix 기반의 깔끔한 UI를 간단하게 구현하고 싶다면, shadcn/ui는 훌륭한 출발점입니다.

과거의 습관: JSP에서의 리다이렉트

전통적인 JSP, Servlet 기반 웹 프로젝트에서는 페이지 전환이 필요할 때 아래와 같은 코드를 자주 사용했습니다.

<% response.sendRedirect("/login.jsp"); %>

혹은 JavaScript로 직접 URL을 바꾸는 방식도 흔했죠.

<script>
  location.href = "/home.jsp";
</script>

이 방식은 페이지 전체를 서버에 다시 요청해서 새로 고침합니다. 다시 말하면, HTML, CSS, JS, 이미지까지 모든 자원이 다시 로드되기 때문에 UX 관점에서 불편함이 있습니다.


왜 문제가 될까?

문제점 설명
전체 새로고침 발생 화면이 깜빡이며 사용자 경험이 떨어집니다
느린 페이지 전환 매 요청마다 서버에서 다시 렌더링
클라이언트 상태 유실 로그인 여부나 입력값 등이 초기화될 수 있습니다
SEO에 불리할 수도 불필요한 리다이렉트는 검색 엔진 최적화에 부정적인 영향을 줄 수 있습니다

Next.js & React에서의 전환 방식: Link 컴포넌트

React 기반 SPA(Single Page Application)에서는 페이지 전환조차도 컴포넌트 형태로 처리합니다. 이를 위해 Link 컴포넌트를 사용하면 새로고침 없이도 자연스럽고 빠른 이동이 가능합니다.

Next.js 예제

import Link from 'next/link';

export default function Home() {
  return (
    <div>
      <h1>홈 화면</h1>
      <Link href="/about">
        <a>About 페이지로 이동</a>
      </Link>
    </div>
  );
}

작동 방식

  • 실제 페이지 이동은 JavaScript로 이루어집니다
  • 브라우저는 새로고침 없이 URL만 변경하고 해당 컴포넌트를 불러옵니다
  • React의 Virtual DOM 덕분에 필요한 부분만 로드됩니다

실제 코드 비교

구분 JSP 방식 React 방식
코드 <% response.sendRedirect("/mypage.jsp"); %> <Link href="/mypage"><a>마이페이지</a></Link>
동작 서버로 새 요청 (전체 새로고침) 클라이언트 라우팅 (부분 렌더링)
UX 깜빡임, 느림 부드러움, 빠름
상태 유지 어려움 가능함

어느 경우 Link 대신 리다이렉트가 필요할까?

  • 로그인 후 자동 이동: router.push('/dashboard')
  • 인증 실패 시 서버에서 리다이렉트: getServerSideProps or next.config.js
  • 외부 URL로 이동: window.location.href

하지만! 사용자 UI 클릭으로 인해 이동한다면 **무조건 Link**를 사용해야 합니다.


정리

  • 과거의 sendRedirect, location.href는 서버 중심 사고
  • React/Next.js에서는 클라이언트 중심 전환 방식을 활용하자
  • 새로고침 없는 페이지 전환 = 더 나은 UX + 빠른 반응성
  • 이제는 Link 컴포넌트가 기본이다!

프론트엔드 개발을 하다 보면 한 번쯤은 TypeScript 도입을 고민하게 됩니다. 많은 개발자들이 이미 TypeScript를 표준처럼 사용하고 있지만, "모두가 써야만 한다"고 말하기는 어렵습니다.

이 글에서는 TypeScript의 장단점을 정리하고, 어떤 상황에서 TypeScript가 좋은 선택이 될지, 또는 피하는 것이 나을지 구체적으로 정리해보려 합니다. 개인 프로젝트를 시작하든 팀 프로젝트를 준비하든, 판단 기준이 되어줄 수 있기를 바랍니다.


TypeScript란?

TypeScript는 Microsoft에서 만든 오픈소스 프로그래밍 언어입니다. JavaScript의 상위 집합(Superset)으로, 기존 JavaScript 코드에 정적 타입 시스템을 추가한 것이 특징입니다.

TypeScript로 작성한 코드는 트랜스파일링 과정을 거쳐 JavaScript로 변환되며, 브라우저나 Node.js 환경에서 실행됩니다.

 

 TypeScript의 장점

1. 정적 타입 검사로 버그 예방

컴파일 단계에서 타입 오류를 미리 찾아낼 수 있어, 런타임에서 발생할 수 있는 치명적인 오류를 줄일 수 있습니다. 특히 대규모 프로젝트나 팀 개발 환경에서 안정성을 확보하는 데 큰 장점이 됩니다.

2. 뛰어난 개발자 경험 (IDE 자동완성)

타입 정보 덕분에 코드 자동완성, 타입 추론, 함수 시그니처 보기, 리팩토링 등 IDE 기능이 강화됩니다. 이는 개발 속도뿐 아니라 코드 정확도 향상에도 도움을 줍니다.

3. 가독성과 유지보수성 향상

코드에 타입이 명시되면 구조와 의도가 분명해지고, 자연스럽게 문서화 효과를 얻을 수 있습니다. 협업 시 커뮤니케이션 비용을 줄이고, 시간이 지나도 코드 해석이 쉬워집니다.

4. 최신 JavaScript 문법 지원

TypeScript는 ECMAScript의 최신 문법을 지원하며, 구형 브라우저에서도 작동하도록 변환해줍니다. async/await, optional chaining, nullish coalescing 같은 문법을 빠르게 활용할 수 있습니다.

5. OOP 지원

클래스, 인터페이스, 제네릭 등 객체지향 프로그래밍에 유용한 문법이 내장되어 있어, 자바나 C# 등 정적 언어에 익숙한 개발자도 빠르게 적응할 수 있습니다.


 TypeScript의 단점

1. 컴파일 필요

TypeScript는 JavaScript처럼 브라우저에서 바로 실행되지 않습니다. 매번 빌드 과정을 거쳐야 하기 때문에 간단한 실험이나 빠른 프로토타이핑엔 다소 불편할 수 있습니다.

2. 러닝커브 존재

JavaScript에 익숙하더라도 타입 시스템, 제네릭, 타입 추론 등은 새롭게 익혀야 합니다. 특히 초반에는 문법이 복잡하게 느껴질 수 있습니다.

3. 개발 속도 저하

코드에 타입을 명시하고 오류를 해결하는 데 시간이 소요되다 보니, 짧은 시간 안에 결과물을 내야 하는 상황에서는 오히려 개발 속도가 느려질 수 있습니다.

4. 런타임에서는 타입을 보장하지 못함

TypeScript는 어디까지나 컴파일 타임 타입 검사만 가능하기 때문에, 런타임에 타입 오류가 발생할 수 있습니다. 특히 외부 API 응답이나 JSON 데이터의 구조가 예상과 다를 경우, 다음과 같은 에러가 발생합니다.

 
interface User { name: string; age: number; }
const data = JSON.parse('{ "name": "Alice" }');
const user: User = data; console.log(user.age.toFixed(1));
// 런타임 에러 발생: Cannot read property 'toFixed' of undefined
 

이처럼 타입만 믿고 검증 없이 사용하면 오히려 예상치 못한 에러가 발생할 수 있으므로, 런타임 타입 검증 도구(zod, io-ts 등)와 병행하는 것이 좋습니다.

5. 서드파티 라이브러리의 타입 지원 이슈

대부분의 유명 라이브러리는 타입 정의를 지원하지만, 오래된 라이브러리나 마이너한 패키지는 타입이 부실하거나 아예 없는 경우도 있습니다. 이 경우 직접 타입을 정의하거나, DefinitelyTyped(@types/) 패키지를 별도로 설치해야 할 수 있습니다.


언제 TypeScript를 쓰는 게 좋을까?

TypeScript가 유리한 상황

  • 중·대형 규모의 프로젝트를 진행 중이다
  • 여러 명과 협업하는 프로젝트이다
  • 리팩토링, 테스트, 유지보수 비용이 중요한 프로젝트이다
  • 객체지향 언어에 익숙하거나, 장기적으로 안정적인 코드를 원한다
  • 프론트엔드와 백엔드를 모두 다루는 풀스택 개발이다
  • 개발 문화나 CI/CD 파이프라인이 잘 잡혀 있다

TypeScript를 피하는 게 나은 상황

  • 빠르게 결과를 내야 하는 프로토타이핑 단계
  • 소규모 또는 1인 개발 프로젝트이며, 유지보수 주기가 짧다
  • JavaScript만으로도 충분히 익숙하고 빠르게 개발 가능하다
  • 외부 타입 정의가 미비한 라이브러리를 많이 써야 한다
  • 타입 시스템보다 개발 속도가 더 중요한 프로젝트다

+ Recent posts