Node.js · 2026-04-25

HTTP 캐싱 전략: ETag와 Cache-Control 적용

HTTP 캐싱 기초부터 ETag와 Cache-Control의 차이점, 조건부 요청 처리, Express에서의 Cache-Control Express 설정과 Node ETag 설정 예제, 최적화 적용법을 포함한 실무 중심의 기술문서

작성일 : 2026-04-25 ㆍ 작성자 : 관리자
post
목차

개요

웹 애플리케이션 성능 개선에 캐싱은 필수 요소다. HTTP 레벨 캐시는 응답 재전송을 줄여 지연 시간을 낮추고 대역폭을 절감한다. 이 글에서는 ETag와 Cache-Control의 개념을 명확히 정리하고, Node.js(특히 Express)에 어떻게 적용하는지 단계별 예제를 통해 설명한다.

HTTP 캐싱 기본

캐싱의 목적과 동작

캐싱은 클라이언트나 중간 프록시가 서버 응답을 저장해 동일한 리소스 요청 시 재사용하도록 하는 기법이다. 서버는 응답 헤더로 캐시 정책을 전달하고, 클라이언트는 조건부 요청을 통해 변경 여부를 확인한다.

주요 헤더

  • Cache-Control: 캐시 동작을 세부적으로 제어한다.
  • ETag: 리소스 버전 식별자(해시 또는 토큰)를 제공한다.
  • Last-Modified: 최종 변경 시각을 나타낸다.
  • If-None-Match / If-Modified-Since: 조건부 요청에 사용된다.

ETag 상세

개념

ETag는 리소스의 고유 식별자다. 서버가 응답에 ETag를 담아 보내면 클라이언트는 다음 요청 시 If-None-Match에 이 값을 포함한다. 서버는 값이 동일하면 304 Not Modified로 응답해 본문을 전송하지 않는다.

강한 ETag와 약한 ETag

  • 강한 ETag: 바이트 단위까지 동일해야 한다. 정확한 동등성을 의미.
  • 약한 ETag(weak): W/ 접두어로 표시되며, 의미적으로 동등한 경우에만 같다고 본다.

Cache-Control 상세

주요 디렉티브

  • max-age: 응답을 얼마 동안 재사용할지(초 단위).
  • public / private: 공유 캐시(프록시)에 보관 가능한지 여부.
  • no-cache: 매 요청마다 서버 검증 필요(본문 전송 가능).
  • no-store: 절대 저장 금지.
  • must-revalidate: 만료 시 재검증 강제.

전략 선택

정적 자원(이미지, JS, CSS)은 긴 max-age와 파일명 해싱을 함께 사용해 캐시 수명을 길게 가져간다. 동적 응답은 ETag나 Last-Modified로 조건부 요청을 처리해 불필요한 본문 전송을 줄인다.

Node.js 적용

Express 기본 동작

Express는 기본적으로 ETag를 자동 생성한다. express.static을 사용하면 Cache-Control도 설정 가능하다. 다만 서비스 요구에 따라 명시적으로 제어하는 것이 안전하다.

Cache-Control Express 설정 예제

정적 파일을 30일 동안 캐시하도록 설정하는 예

const express = require('express')
const app = express()

app.use('/static', express.static('public', {
  maxAge: 30 * 24 * 60 * 60 * 1000 // 밀리초 단위
}))

app.listen(3000)

Node ETag 설정 예제: 기본 사용과 비활성화

Express의 ETag 생성 방식을 확인하거나 비활성화하는 방법

const express = require('express')
const app = express()

// 기본 ETag 사용(Express 기본값)
app.get('/resource', (req, res) => {
  res.send('Hello')
})

// ETag 비활성화
app.disable('etag')
app.get('/no-etag', (req, res) => {
  res.send('No ETag')
})

app.listen(3000)

커스텀 ETag 생성 예제

파일 내용이나 객체를 해시하여 ETag를 직접 제어하면 더 세밀한 캐시 정책 적용이 가능하다.

const express = require('express')
const crypto = require('crypto')
const app = express()

function generateETag(body) {
  return crypto.createHash('sha1').update(body).digest('hex')
}

app.get('/custom-etag', (req, res) => {
  const body = 'dynamic content ' + Date.now()
  const etag = generateETag(body)

  res.set('ETag', etag)

  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end()
  }

  res.send(body)
})

app.listen(3000)

조건부 요청 흐름 요약

  • 클라이언트가 최초 요청을 보냄. 서버는 ETag와 Cache-Control을 응답함.
  • 클라이언트가 만료 전이면 로컬 캐시 사용. 만료거나 no-cache면 If-None-Match 포함 재요청.
  • 서버가 변경 없으면 304 응답. 변경 있으면 200과 본문 전송.

권장 실무 전략

  • 정적 자원은 파일명 해시(예: app.v123.css) + 긴 max-age 사용.
  • 동적 응답은 ETag 또는 Last-Modified로 조건부 요청 처리.
  • 프라이버시 민감한 응답은 private 또는 no-store 설정.
  • 복잡한 리소스는 커스텀 ETag로 정확한 변경 감지 적용.

결론

ETag와 Cache-Control은 서로 보완하는 도구다. Cache-Control로 보관 정책을 정하고, ETag로 변경 여부를 검증하면 네트워크와 서버 리소스를 함께 절약할 수 있다. Node.js 환경에서는 Express 기본 기능을 활용하되, 서비스 특성에 맞춰 명시적 설정과 필요 시 커스텀 ETag 적용을 고려한다.

Node ETag 설정 예제 Cache-Control Express 설정 HTTP 캐싱 전략 Node.js ETag Cache-Control Express 캐시 설정 조건부 요청 정적 파일 캐싱