Front-End/React

T3 Stack에서 Websocket 동시 적용하기

jakejeong 2025. 2. 23. 20:56

들어가며

T3는 타입스크립트 기반으로 NextJS, tRPC, tailwind 등의 주요 라이브러리들을 사전에 조합해서 풀스택 웹 앱을 만들 수 있게 해주는 개발 툴킷이다.

현대 웹 프레임워크는 Front-End와 Back-End가 분리되어 구성된 형태가 표준으로 자리잡았다.

그러나 빠르게 MVP를 출시해야 하는 스타트업이나 소규모 개발팀에서는 Front와 Back을 나누는 행위 자체가 번거로울 때가 있다.

MAU가 많지 않고, 비즈니스 로직이 복잡하지 않는데 프로그램 구조를 표준에 맞추다보니 오히려 개발 공수가 늘어난다.

이러한 문제를 해결하기 위해 일부 타입스크립트 개발자들을 중심으로 풀스택 어플리케이션을 구현하는 움직임이 생겼다.

여기서 말하는 풀스택은 레거시라 불리는 JSP / SRPING의 디자인 패턴이 아닌 현대 웹 개발에 주요한 역할을 하는 라이브러리들을 조합해서 표준 방식의 장점을 고루 갖춘 풀스택을 의미한다.

작성자는 논문 작성을 위해 웹 기반 실시간 동시 드로잉 플랫폼을 구현하게 되었는데, 예산과 기간을 따져보았을 때 표준 구현 방법론을 적용하기에는 많은 무리가 있었고, T3 Stack을 선택하게 되었다.

문제 소개

이번 구현 작업에서 T3 Stack 서버에 동일 포트로 WebSocket을 추가하는 과제가 발생하였다.

T3 Stack과 같은 사전 구성이 완료된 툴킷에 추가 기능을 구현하는 것은 고려할 점이 많다.

작성자가 해당 과제를 어떤 방식으로 해결하였는지 소개하고 비슷한 과제를 고민하는 개발자들에게 도움이 되면 좋겠다는 취지로 글을 작성하였다.

 


서론: 왜 이 방법이 필요한가?

최신 웹 애플리케이션에서 실시간 통신은 필수 기능이 되었다. 채팅, 주식 시세, 실시간 협업 도구 등 다양한 시나리오에서 WebSocket 기술이 필요하다. 하지만 Next.js 기반의 T3 Stack 프로젝트에서 WebSocket을 별도 서버 없이 통합하는 것은 생각보다 까다로운 작업이다. 이 글에서는 단일 포트(3000)에서 Next.js HTTP 서버와 WebSocket을 동시에 운영하는 솔루션을 단계별로 설명한다.


1. 시작 전 준비사항

  • T3 Stack 프로젝트 (create-t3-app으로 생성)
  • Node.js v18+ (ES Modules 필수)
  • 기본 WebSocket 지식
  • Socket.io 라이브러리 (웹소켓 구현체)

2. 핵심 원리: 단일 포트 이중 프로토콜

구현의 핵심은 단일 포트로 HTTP(NextJS)와 WebSocket(Socket IO)를 동시에 취하는 것이다.

graph TD
    A[Client] -->|HTTP| B(포트 3000)
    A -->|WebSocket| B
    B --> C[Next.js 서버]
    B --> D[Socket.io 서버]
    C & D --> E[공통 HTTP 인스턴스]

3. 단계별 구현 가이드

t3 Stack app이 구현되어 있다는 전제로 웹소켓 구현을 진행한다.

1) 필수 패키지 설치

npm install socket.io socket.io-client @types/socket.io

2) 커스텀 서버 구성 (server.ts)

exprees로 웹소켓을 구현해본 개발자라면 본 코드가 익숙할 것이다.

우리의 목적은 NextJS 서버와 웹소켓 서버를 동시에 실행하는 것인데 t3 Stack에서 기본 제공하는 내장 실행 구문을 사용하면 서버를 커스터마이징 할 수가 없다.

 

그렇기 때문에 node JS 서버 모듈을 이용해서 별도의 커스터마이징 서버를 구성한다.

// 프로젝트 루트에 생성
import { createServer } from 'http';
import next from 'next';
import { Server } from 'socket.io';

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const port = process.env.PORT || 3000;

app.prepare().then(() => {
  const httpServer = createServer((req, res) => {
    handle(req, res);
  });

  // Socket.io 서버 초기화
  const io = new Server(httpServer, {
    cors: {
      origin: dev ? 'http://localhost:3000' : '프로덕션 도메인',
      methods: ['GET', 'POST']
    }
  });

  // 실시간 이벤트 핸들링
  io.on('connection', (socket) => {
    console.log(`새 연결: ${socket.id}`);

    socket.on('message', (data) => {
      io.emit('broadcast', data); // 전체 브로드캐스트
    });

    socket.on('disconnect', () => {
      console.log(`${socket.id} 연결 종료`);
    });
  });

  httpServer.listen(port, () => {
    console.log(`> 서버 실행: http://localhost:${port}`);
  });
});

3) 클라이언트 연결 로직

클라이언트에서는 Socket IO Client를 통해서 연결을 수행한다.

// components/Chat.tsx
import { useEffect } from 'react';
import { io } from 'socket.io-client';

const SOCKET_URL = process.env.NODE_ENV === 'production' 
  ? 'https://your-domain.com' 
  : 'http://localhost:3000';

export default function Chat() {
  useEffect(() => {
    const socket = io(SOCKET_URL);

    socket.on('connect', () => {
      console.log('소켓 연결 성공!');
    });

    socket.on('broadcast', (data) => {
      console.log('새 메시지:', data);
    });

    return () => {
      socket.disconnect();
    };
  }, []);

  return (
    <div>
      {/* 채팅 UI 구현 */}
    </div>
  );
}

4) 필수 설정 변경

커스터마이징한 내용에 맞게 run script를 수정해줘야 한다. 

Node JS는 기본적으로 Common JS 모듈을 사용하는데 ts는 ESM 모듈이다.

따라서 Node JS가 ts 파일을 해석할 때는 별도의 로더와 파일을 지정해야 한다.

 

package.json 수정

{
  "scripts": {
    "dev": "node --loader ts-node/esm server.ts",
    "build": "next build",
    "start": "NODE_ENV=production node --loader ts-node/esm server.ts"
  }
}

tsconfig.json 추가

nodejs가 ts를 esm으로 해석할 수 있게 config 파일을 수정해준다.

{
  "compilerOptions": {
    "module": "CommonJS",
    "esModuleInterop": true,
    "target": "ES2020"
  },
  "ts-node": {
    "esm": true
  }
}

4. 주요 문제 해결 전략

사소한 설정이지만 지키지 않을 경우 문제가 발생할 수 있다.

1) 파일 위치 오류

  • server.ts는 반드시 프로젝트 루트에 위치
  • next.config.js와 동일 레벨

2) Node.js 버전 문제

# 버전 확인 (18.x 이상 권장)
node -v

# nvm 사용시
nvm install 18
nvm use 18

3) CORS 설정

const io = new Server(httpServer, {
  cors: {
    origin: [
      "http://localhost:3000",
      "https://your-production-domain.com"
    ],
    credentials: true
  }
});

5. 프로덕션 배포 시 주의사항

NextJS 앱 호스팅으로 자주 쓰는 vercel은 웹소켓을 지원하지 않는다.

프로덕션 앱에서 이 점을 주의해야 한다.

1) 호스팅 선택

  • Vercel: WebSocket 미지원 → 사용 불가
  • 권장 서비스:
    • AWS EC2
    • DigitalOcean Droplet
    • Railway
    • Heroku

2) 환경 변수 설정

# .env
PORT=3000
NEXT_PUBLIC_WS_URL=wss://your-domain.com

 

6. FAQ: 자주 묻는 질문

Q1. Next.js API 라우트와 충돌나지 않나요?
→ 기존 /api/* 라우트는 정상 작동하며 WebSocket은 /socket.io/* 경로를 사용합니다.

Q2. 개발 중 Hot Reload가 안 돼요
nodemon 설정 추가 권장:

{
  "scripts": {
    "dev": "nodemon --exec node --loader ts-node/esm server.ts"
  }
}

Q3. 동시 접속자 수 제한은?
→ 기본적으로 Node.js의 메모리 한계까지 가능


결론: 아키텍쳐 정리

sequenceDiagram
    participant Client
    participant Next.js
    participant Socket.io
    participant HTTP Server

    Client->>HTTP Server: HTTP Request (Next.js)
    Client->>HTTP Server: WebSocket Upgrade
    HTTP Server->>Next.js: 페이지 요청 처리
    HTTP Server->>Socket.io: WebSocket 연결 처리
    Socket.io->>Client: 실시간 이벤트 전송

 

지금까지 t3 Stack에서 웹소켓을 동시에 구현하는 방법을 살펴보았다.

앞서 설명했듯이 신속한 개발 성과를 달성해야 하는 팀이 아니라면 각각의 서버를 분리하는 것이 일반적이다.

 

감사합니다.

 

반응형