운영 관리 화면을 개발하다 보면 어김없이 "엑셀 다운로드" 기능 요건이 들어온다. 단순히 "어디서 파일을 만드느냐"의 차이처럼 보이지만, 실제로는 어떤 데이터를 진짜 기준으로 삼느냐의 문제이기도 하다.
구현 방법은 크게 세 가지로 나뉜다. 어떤 상황에서 어떤 방식을 골라야 하는지 정리한다.
📌 세 가지 방식 한눈에 보기
방식 파일 생성 위치 핵심 특징
| Client Export | 브라우저 | 이미 내려온 데이터를 프론트에서 읽어 즉시 파일 생성. exceljs, SheetJS 등 활용 |
| Server Download | 서버 | 요청 시 서버가 데이터를 다시 조회하고 파일을 생성해 응답으로 반환 |
| Hybrid | 서버 | 화면의 정렬·컬럼 상태를 파라미터로 서버에 전달, 서버가 전체 데이터 기준으로 파일 생성 |
📌 1. Client Export
브라우저가 이미 가지고 있는 데이터를 프론트엔드에서 직접 엑셀 파일로 변환하는 방식이다. 별도의 다운로드 API 없이 프론트 단에서 완결된다.
동작 흐름
현재 렌더링된 row 데이터 읽기
→ exceljs / SheetJS로 Workbook 생성
→ Blob + a[download]로 즉시 저장
// exceljs를 이용한 클라이언트 엑셀 생성 예시
import ExcelJS from 'exceljs';
export async function exportToExcel(columns, rows, filename) {
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('Sheet1');
// 현재 화면의 컬럼 구성을 그대로 반영
sheet.columns = columns.map(col => ({
header: col.label,
key: col.key,
width: col.width ?? 15,
}));
rows.forEach(row => sheet.addRow(row));
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${filename}.xlsx`;
a.click();
URL.revokeObjectURL(url);
}
장단점
✅ 장점 ❌ 단점
| 별도 API 개발 불필요 | 브라우저에 내려온 데이터만 가능 |
| 구현 속도 빠름 | 페이지네이션 환경에서 전체 데이터 불가 |
| 화면 상태 그대로 반영 | 대용량 시 메모리 부담 큼 |
| 서버 부하 없음 | 권한 제어, 감사 로그 불가 |
언제 쓰면 좋을까? 데이터 양이 많지 않고 "지금 화면에 보이는 대로 저장"이 목적인 경우. 내부 운영툴에서 빠른 구현이 우선이거나, 서버 측 다운로드 API를 별도로 만들 비용이 크지 않을 때 유용하다.
📌 2. Server Download
사용자가 다운로드를 요청하면 서버가 데이터를 다시 조회하고 엑셀 파일을 생성해 응답으로 반환하는 방식이다. 파일 생성의 책임과 데이터의 기준이 서버에 있다.
동작 흐름
프론트가 파라미터 포함 GET 요청
→ 서버 권한 검증
→ 전체 데이터 재조회
→ 파일 생성
→ Content-Disposition: attachment로 응답
// 프론트: 현재 화면 상태를 파라미터로 전달
const downloadExcel = async () => {
const params = new URLSearchParams({
sortBy: currentSort.column,
sortDirection: currentSort.direction,
columns: visibleColumns.join(','),
});
const res = await fetch(`/api/items/excel?${params}`);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'export.xlsx';
a.click();
};
// 서버(Node.js/Express 예시): 전체 데이터 조회 후 파일 생성
app.get('/api/items/excel', async (req, res) => {
const { sortBy, sortDirection, columns } = req.query;
const data = await db.findAll({ sort: { [sortBy]: sortDirection } });
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('결과');
// ... 컬럼 및 데이터 구성 ...
res.setHeader('Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition',
'attachment; filename="export.xlsx"');
await workbook.xlsx.write(res);
res.end();
});
장단점
✅ 장점 ❌ 단점
| 전체 데이터 다운로드 보장 | 백엔드 API 개발 필요 |
| 대용량 처리에 유리 | 서버 부하 및 응답 시간 관리 필요 |
| 권한·감사 로그 통제 가능 | 화면 상태 → 파라미터 정합성 유지 필요 |
| 다중 시트, 집계, 복잡 서식 구현 용이 | 구현 범위가 프론트 단독보다 넓음 |
언제 쓰면 좋을까? 페이지네이션이 있어 화면에는 일부만 보이지만 엑셀은 전체를 받아야 할 때. 다운로드 결과가 보고·정산·검증의 근거 자료로 쓰이는 경우. 권한에 따라 노출 컬럼이 달라지거나 감사 로그가 필요한 운영 화면.
📌 3. Hybrid 방식
화면의 UI 상태(정렬, 필터, 컬럼 노출)는 프론트가 들고 있고, 실제 파일 생성과 데이터 조회는 서버가 담당하는 절충형이다. 운영 관리 화면에서 가장 많이 쓰이는 방식이기도 하다.
핵심 아이디어 화면 상태를 파라미터로 서버에 전달 → 서버는 해당 조건으로 전체 데이터 재조회 → 파일 생성 → 반환. Server Download와 구조는 같고, "화면 상태를 얼마나 정확히 반영하느냐"에 초점이 있다.
구현 시 신경 써야 할 것들
- 프론트의 컬럼 ID와 서버의 컬럼 키가 일치해야 한다
- 클라이언트 로컬 필터와 서버 필터를 명확히 구분해야 결과 불일치를 막을 수 있다
- 정렬 기준, 필터 조건, 노출 컬럼을 파라미터 계약으로 명문화하는 편이 유지보수에 유리하다
장단점
✅ 장점 ❌ 단점
| "현재 화면 기준" 경험 유지 | 프론트-백 파라미터 계약 복잡 |
| 전체 데이터 신뢰성 확보 | 컬럼 키 동기화 유지 필요 |
| UI 일관성 + 대용량 대응 동시에 | 로컬/서버 필터 혼재 시 결과 불일치 위험 |
🔍 세 방식 비교
항목 Client Export Server Download Hybrid
| 데이터 범위 | 화면 범위만 | ✅ 전체 보장 | ✅ 전체 보장 |
| 화면 상태 반영 | ✅ 그대로 | 파라미터로 | 파라미터로 |
| 권한 / 감사 로그 | ❌ 불가 | ✅ 가능 | ✅ 가능 |
| 대용량 대응 | ❌ 한계 있음 | ✅ 유리 | ✅ 유리 |
| 구현 복잡도 | ✅ 낮음 | 중간 | 높음 |
| 백엔드 필요 | ✅ 불필요 | 필요 | 필요 |
📋 실무 선택 기준
아래 질문 중 하나라도 "예"에 해당한다면 Server Download 또는 Hybrid 방식을 검토하는 편이 안전하다.
- 화면에는 일부만 보이지만 엑셀은 전체 데이터를 받아야 하는가?
- 다운로드 결과가 보고, 정산, 검증의 근거 자료로 사용되는가?
- 사용자 권한에 따라 포함되는 컬럼이 달라질 수 있는가?
- 행 수가 수천~수만 건 이상으로 늘어날 가능성이 있는가?
- 향후 다중 시트, 셀 포맷, 집계 행, 파일명 정책 등이 추가될 가능성이 있는가?
반대로 아래 조건이라면 Client Export도 충분히 좋은 선택이다.
- 다운로드는 부가 기능이고, "지금 보이는 것 저장"으로 충분하다
- 데이터 건수가 많지 않다 (수백 건 이내)
- 공통 UI 컴포넌트에 빠르게 기본 기능을 추가하고 싶다
- 서버 API 개발 없이 프론트 단에서 빠르게 제공하는 것이 우선이다
⚠️ 실무에서 자주 발생하는 실수
공통 DataTable 컴포넌트에는 Client Export를 기본으로 탑재하고, 특정 도메인 화면에서는 공통 버튼을 비활성화하거나 숨긴 뒤 전용 Server Download 버튼으로 교체하는 방식이 현장에서 많이 쓰인다.
⚠️ 페이지네이션 화면에서 Client Export를 그대로 두면 안 된다 페이지네이션이 적용된 화면에서 Client Export를 그대로 쓰면 "1페이지 데이터만 저장"되는 상황이 생긴다. 사용자는 전체를 받았다고 생각하지만 실제론 그렇지 않다. 운영 현장에서 가장 자주 발생하는 사고 유형 중 하나다.
📋 한 줄 요약
방식 선택 기준
| Client Export | 화면에 보이는 범위를 빠르게 저장하면 충분할 때 |
| Server Download | 전체 데이터, 권한 제어, 대용량, 공식 산출물이 필요할 때 |
| Hybrid | UI 상태 반영과 서버 데이터 신뢰성을 둘 다 원할 때 |