ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Nginx 없이 Next.js 프론트엔드를 운영할 수 있을까?
    Front-end/Next.js 2025. 12. 28. 20:04
    반응형

    Nginx 없이 Next.js 프론트엔드를 운영할 수 있을까?

     

     

    매번 프로젝트를 할 때 Next.js와 Nginx를 함께 사용해왔다.

    그러나 이번에는 Nginx를 사용하지 않기로 했다.

    Nginx 없이 프론트엔드를 운영하는데 문제가 없을까?

     


     

    프론트엔드 개발을 하다보면 "배포" 라는 단계에서 Nginx를 만나게 된다.

    정적 파일을 서빙하려면 Nginx가 필요하다고 배웠고, 실제로 많은 프로젝트에서 그렇게 사용하고 있다.

    그런데 이번에 프로젝트를 하면서 Nginx 없이 프로젝트를 구성하게 되었다.

    이런 방식에 대한 이름까지 있었는데 "Nginx-less Modern Stack" 이라는 패턴이었다.

     


    Nginx란 무엇인가?

    Nginx는 웹 서버 소프트웨어이다. 유저가 브라우저에 주소를 입력했을 때 그에 맞는 파일이나 데이터를 돌려주는 프로그램이다.

    쉽게 비유하자면, 식당의 홀 직원 같은 것이다.

    홀 직원 없이도 주방에서 직접 만든 것을 서빙할 수 있지만 손님이 많아지면 손님과 주방 사이에 홀 직원이 있는 것이 훨씬 효율적일 것이다.

     

    Nginx가 하던 일들

    전통적인 웹 서비스 구조에서 Nginx는 많은 일을 해왔다.

     

    1. 정적 파일 서빙

    React, Vue에서 npm run build를 하면 dist 또는 build 폴더가 생성되면서 내부에 HTML, CSS, Javascript 파일이 생성된다.

    이 파일들은 한번 만들어지면 변하지 않는다. 사용자가 누구든, 언제 요청하든 똑같은 파일을 돌려준다.

    이를 "정적 파일" 이라고 부른다.

     

    왜 Nginx가 좋았는가?

    Node.js로도 정적 파일을 서빙할 수 있다.

    express.static() 같은 것을 사용하면 된다. 그러나 Nginx를 사용하는 이유는 훨씬 빠르기 때문이다.

    Node.js는 Javascript 코드를 실행하는 런타임인데 파일 하나를 보내려면 Javascript 코드가 실행되어야한다.

    반면, Nginx의 경우 C언어로 만들어져 파일 서빙에 최적화 되어있다.

     

    2. Reverse Proxy (리버스 프록시)

    Proxy라는 것은 "대리인"을 말한다. 즉, A가 직접 C이게 말하는 것이 아니라 중간에 B를 두고 B가 대신 전달하게 하는 방식이다.

     

    왜 필요한가?

    프론트엔드에서 백엔드 API를 호출할 때 문제가 있다.

    프론트엔드 : https://my-app.com
    백엔드 : https://api.my-app.com

     

    브라우저에는 SOP(동일 출처 정책)라는 보안 규칙이 있다.

    쉽게 말해, my-app.com에서 로드된 Javascript가 api.my-app.com으로 요청을 보내면 도메인이 다르기 때문에 브라우저가 막아버린다. 이걸 CORS(Corss-Origin Resource Sharing) 문제라고 한다.

    백엔드에서 특별한 헤더를 설정해서 허용해줄 수도 있지만 다른 방법도 있다.

     

    Nginx가 대신 한다!

    브라우저 → Nginx (my-app.com/api) → 백엔드 (api.my-app.com)

     

    Nginx를 설정해서 my-app.com/api/... 로 오는 요청을 api.my-app.com/... 으로 전달하게 한다.

    브라우저 입장에서는 같은 도메인으로 요청하는 것처럼 보이므로 CORS 문제를 막을 수 있는 것이다.

    이렇게 클라이언트의 요청을 받아서 다른 서버로 전달하는 것을 Reverse Proxy 라고 부른다.

     

    3. SSL/HTTPS

    HTTP 뒤에 S가 붙으면 Secure를 의미한다. 즉, 암호화 된 통신을 한다는 것이다.

    브라우저와 서버 사이에 오가는 데이터를 암호화해서 중간에서 누가 훔쳐봐도 내용을 알 수 없게 한다.

    Chrome 브라우저는 HTTP 사이트에 "안전하지 않음" 경고를 띄우고, 검색 엔진도 HTTPS 사이트를 더 높게 평가하기 때문에 거의 필수라고 할 수 있다.

     

    이런 HTTPS를 사용하려면 SSL 인증서가 필요하다.

    이 인증서는 "이 서버가 진짜 my-app.com이 맞습니다!" 라고 증명해주는 디지털 문서라고 볼 수 있다.

    Let's Encrypt라는 서비스에서 무료로 발급 받을 수 있다. 이 인증서를 서버에 설정하는 것이 꽤 번거로운데 전통적으로 이 작업은 Nginx에서 했다.

    server {
        listen 443 ssl;
        ssl_certificate /etc/letsencrypt/live/my-app.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/my-app.com/privkey.pem;
        ...
    }

     

    4. Load Balance

    서버 한 대로는 부족할 때는?

    사용자가 많아지면서 서버 한 대로는 감당이 안되는 경우가 있다. 이 때는 서버를 여러 대로 늘려야하는데 그러면 새로운 문제가 발생하게 된다. 바로 사용자의 요청을 어느 서버로 보내야하지? 에 대한 것이다.

    그리고 이를 도와줄 수 있는 것이 로드밸런싱이며, Nginx에서 분배를 도와줄 수 있다.

    upstream backend {
        server server1.example.com;
        server server2.example.com;
        server server3.example.com;
    }

     

    이렇게 설정해두면 Nginx가 요청을 세 개의 서버에 골고루 분배해준다. 한 서버가 죽어도 나머지 서버가 처리하니까 서비스가 중단되지 않는다는 장점이 있다.

     

    5. 보안 헤더 설정

    웹 보안을 위해 서버가 브라우저에게 보내는 특별한 헤더들이 있다.

    • X-Frame-Options : 내 사이트를 다른 사이트의 iframe에 넣지 못하도록 한다. 클릭 재킹 공격을 방지할 수 있다.
    • X-Content_Type-Options : 브라우저가 파일 타입을 임의로 추측하지 못하게 한다.
    • Content-Secureity-Policy : 어떤 외부 리소스를 로드할 수 있는지 정의한다.

    이러한 헤더들도 Nginx에서 설정할 수 있다.

    add_header X-Frame-Options "DENY";
    add_header X-Content-Type-Options "nosniff";

     


     

    이제 세상이 바뀌었다!

    2010년대 중반부터 클라우스 서비스가 보편화 되면서 상황이 많이 달라졌다.

     

    AWS 같은 클라우드 등장

    예전에는 서버를 직접 사거나 빌려서 데이터센터에 두었는데 그러다보니 서버 설치, OS 설정, 네트워크 구성을 전부 직접 해야만 했다.

    당연히 Nginx 같은 웹 서버도 직접 설치하고 관리했다.

    하지만 AWS, Google Cloud, Azure 같은 클라우드 서비스가 나오면서 이런 Infra 작업을 서비스로 제공하기 시작했다.

     

    SSL/HTTPS?

    AWS에서는 ALB(Application Load Balancer)라는 서비스가 있다. 이 로드밸러서에 SSL 인증서를 연결하면 된다. 

    ACM(AWS Certificate Manager)에서 무료로 인증서를 발급 받아서 몇 번의 클릭으로 연결할 수 있다.

     

    로드 밸런싱?

    이름 그대로 로드 밸러서가 해준다. ALB가 여러 서버로 트래픽을 분산시킨다. 오토스케일링 그룹과 연결하면 트래픽이 늘어날 때 서버가 자동으로 추가되기도 한다.

     

    정적 파일 서빙?

    CloudFront라는 CDN서비스가 있다. 전 세계 수백 개의 서버에 파일을 캐싱해두고, 사용자와 가장 가까운 서버에서 응답한다.

    한국 사용자가 요청하면 한국서버에서, 미국 사용자가 요청하면 미국 서버에서 응답하니 엄청 빠른 응답을 받을 수 있는데 Nginx 보다 훨씬 빠르고 트래픽이 아무리 많아도 걱정없다.

     

    보안?

    AWS WAF(Web Application Firewall)가 있다. DDoS 공격 방어, SQL Injection 차단, Rate Limiting(초당 요청 수 제한) 같은 걸 설정 몇 번으로 할 수 있다.

     

    정리하자면....

    Nginx가 하던 일 클라우드에서는...
    SSL 인증서 관리 ALB + ACM
    로드 밸런싱 ALB
    정적 파일 캐싱 CloudFront
    보안 WAF

     


     

    Next.js의 등장

    여기에 더해서 Next.js가 등장하면서 상황이 많이 달라졌다.

     

    예전 방식: React build → Nginx 서빙

    1. npm run build → dist/ 폴더에 정적 파일 생성
    2. dist/ 폴더를 Nginx의 /var/www/html에 복사
    3. Nginx가 정적 파일 서빙

     

    이 방식에서는 Nginx가 필수이다. 빌드 결과물이 그냥 HTML, CSS, JS 파일이니까 누군가 서빙해줘야한다.

     

    Next.js 방식: 서버 내장

    next build → .next/ 폴더 생성
    node .next/standalone/server.js → 서버 실행

     

    Next.js의 경우 빌드하면 Node.js 서버가 함께 나온다.

    Node.js 하나로 앱이 돌아가고 정적 파일 서빙도 이 서버가 하게 된다.

     

    Standalone build

    // next.config.js
    module.exports = {
      output: 'standalone'
    }

     

    Next.js 12버전부터는 standalone mode가 도입되었다.

    이렇게 설정하고 빌드하면 실행에 필요한 모든 것이 .next/standalone 폴더에 들어가게 된다.

    .next/standalone/
    ├── server.js          # 실행할 서버
    ├── .next/             # 빌드된 앱
    │   └── static/        # 정적 파일 (JS, CSS)
    └── node_modules/      # 필요한 의존성만 (용량 최소화)

     

    Docker image로 만들 때 이 폴더만 복사하면 된다. 

    항상 node_modules가 무겁다는 문제가 있었는데 이런 방식을 사용하면 훨씬 가볍다는 장점이 있다.

     

    API Routes로 프록시 역할까지...?!

    Next.js에는 API Routes라는 기능이 있다. /pages/api/ 또는 /app/api/ 폴더에 파일을 만들면 서버 사이드 API 엔드포인트가 된다.

    이걸 활용하면 BFF(Backend For Frontend) 패턴을 구현할 수 있다.

     

    BFF란?

    프론트엔드 전용 백엔드를 말한다. 브라우저와 실제 백엔드 사이에 중간 서버를 두는 패턴이다.

    브라우저 → Next.js(프론트엔드 서버) → Spring Boot(백엔드 API)
              └─ /api/proxy/* ─────────┘

     

    브라우저가 /api/users를 호출하면:

    1. Next.js 서버가 이 요청을 받는다.
    2. Next.js 서버가 실제 백엔드 API (https://api.example.com/users)를 호출한다.
    3. 응답을 받아서 브라우저에 돌려준다.

    이런 방법의 장점은...?

    1. CORS 문제 없음: 브라우저는 같은 도메인(my-app.com)으로만 요청하니까 CORS 문제가 없다.
    2. 토큰 보안: 인증 토큰을 httpOnly 쿠키에 저장하면 Javascript로 접근할 수 없어서 XSS 공격에 안전하다.
    3. API 추상화: 백엔드 API 구조가 바뀌어도 BFF 레이어에서 변환하면 되니 프론트엔드 코드를 바꾸지 않아도 된다.

    이런 역할을 예전에는 Nginx에서 했는데 이제는 Next.js에서 할 수 있는 것이다.

     

    Middleware로 보안 헤더도 설정

    Next.js 12버전부터는 Middleware라는 기능도 생겼는데 모든 요청에 대해 실행되는 코드를 작성할 수 있다.

    // middleware.ts
    import { NextResponse } from 'next/server';
    
    export function middleware(request) {
      const response = NextResponse.next();
    
      // 보안 헤더 추가
      response.headers.set('X-Frame-Options', 'DENY');
      response.headers.set('X-Content-Type-Options', 'nosniff');
      response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
    
      return response;
    }

    Nginx 설정 파일에 쓰던 것을 Javascript로 사용할 수 있게 된 것이다.

    오히려 더 유연하게 작성할 수 있으며 조건데 따라 다른 헤더를 추가하거나 욫엉을 분석해서 처리를 다르게 할 수 있다.

     

    Next.js 16버전부터는 Proxy로 이름이 변경되었다.

     

    왜 이름이 바뀌었을까?

     

    "Middleware" 라는 이름이 실제 역할을 잘 설명하지 못했기 때문이다.

    이 기능은 사실 클라이언트와 서버 사이에서 요청을 가로채고 처리하는 Proxy 역할을 한다.

    즉, Nginx가 하던 일과 같은 것이다.

    그래서 더 직관적인 이름인 Proxy로 변경되었다.

    // proxy.ts (Next.js 16+)
    import { NextRequest, NextResponse } from 'next/server';
    
    export function proxy(request: NextRequest) {
      const response = NextResponse.next();
    
      // 보안 헤더 추가
      response.headers.set('X-Frame-Options', 'DENY');
      response.headers.set('X-Content-Type-Options', 'nosniff');
      response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
    
      return response;
    }
    
    export const config = {
      matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
    };

     

    Next.js 16버전의 Proxy를 사용하게 되면 동적으로 보안 정책을 생성할 수 있는데 이런 동적인 보안 설정은 Nginx만으로는 어렵다.

    Javascript로 작성하니 훨씬 유연하게 할 수 있는 것이다.

     

     


     

    그래서... Nginx는 필요 없는 것인가?

    사실 "상황에 따라 다르다" 라고 할 수 있다.

     

    Nginx가 필요한 경우

    1. On-Premise 환경

    직접 서버를 운영한다면 Nginx가 필요할 것이다.

    클라우드 서비스가 없으니 SSL, 로드 밸런싱, 캐싱을 뭔가가 해줘야하는데 이를 Nginx가 해줄 수 있을 것이다.

     

    2. 복잡한 URL re-writing

    # 이런 복잡한 규칙이 필요하다면
    rewrite ^/old-path/(.*)$ /new-path/$1 permanent;
    rewrite ^/product/([0-9]+)/detail$ /products?id=$1 last;

    Next.js의 rewrites도 있지만, Nginx만큼 강력하진 않다.

     

    3. 여러 앱을 하나의 도메인에서 서빙이 필요한 경우

    location /app1 { proxy_pass http://app1-server; }
    location /app2 { proxy_pass http://app2-server; }
    location /api { proxy_pass http://api-server; }

    위의 예시처럼 여러 프로젝트를 하나의 도메인에서 서빙해야한다면 Nginx가 편할 수 있다.

     

    4. 극도의 성능이 필요할 때

    정적 파일 서빙만 놓고 보면 Nginx가 Node.js보다 빠른 것은 사실이다.

    초당 수만 건의 요청을 처리해야 한다면 고려해볼만하다.

    (물론 트래픽이 과하다면 CDN을 사용하는 것이 맞다)

     

     

     


     

     

    "프론트엔드 배포에는 Nginx가 필수다" 라는 것은 이제는 아니라고 할 수 있겠다.

    클라우드 서비스가 지금처럼 발달하지 않았고, React가 정적 파일을 뱉어내는 라이브러리였고, 서버를 직접 관리하는 것이 일반적이었던 예전에는 "필수"라고 할 수 있었지만 지금은 달라졌다.

    클라우드가 SSL, 로드밸런싱, CDN, WAF를 제공하고, Next.js가 서버 기능, API Proxy, 보안 헤더 설정을 내장하고, Kubernetes가 컨테이너 오케스트레이션을 해주면서 각 레이어가 자기 역할을 명확하게 수행하고 있으니 Nginx라는 만능도구가 필요없어졌다.

    그러나 절대 사용하지 말라는 뜻이 아니며 상황에 맞게, 생각해보고 사용하자! 라는 결론을 내릴 수 있겠다.

     

    반응형
Designed by Tistory.