쇼핑몰을 운영하면서 가장 많이 받는 고객 문의가 무엇일까요? "제 택배 어디쯤 왔나요?"입니다. 배송조회 페이지를 직접 만들어 고객이 스스로 확인할 수 있게 하면 CS 문의를 크게 줄이고 고객 만족도도 높일 수 있습니다.
💡 이 가이드에서 만드는 것
• 백엔드: API 키를 안전하게 관리하는 프록시 서버
• 프론트엔드: 택배사 선택 + 송장번호 입력 폼
• UI: 배송 진행 상황을 보여주는 타임라인
• 에러 처리: 사용자 친화적 에러 메시지
• 백엔드: API 키를 안전하게 관리하는 프록시 서버
• 프론트엔드: 택배사 선택 + 송장번호 입력 폼
• UI: 배송 진행 상황을 보여주는 타임라인
• 에러 처리: 사용자 친화적 에러 메시지
왜 배송조회 페이지가 필요한가?
| 항목 | 배송조회 없이 | 배송조회 추가 후 |
|---|---|---|
| 고객 문의 | "배송 어디예요?" 하루 30건+ | 고객이 직접 확인, 문의 80% 감소 |
| 고객 경험 | 택배사 사이트로 이탈 | 내 쇼핑몰 안에서 확인 |
| 사이트 체류 | 외부 사이트 이동 | 체류 시간 증가, 재구매 유도 |
1백엔드: API 프록시 만들기
⚠️ 왜 서버를 거쳐야 하나요?
API 키를 프론트엔드 코드에 직접 넣으면 누구나 개발자 도구에서 볼 수 있습니다. 반드시 백엔드 서버를 통해 API를 호출하세요.
API 키를 프론트엔드 코드에 직접 넣으면 누구나 개발자 도구에서 볼 수 있습니다. 반드시 백엔드 서버를 통해 API를 호출하세요.
Express.js로 구현하기
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
// 환경변수에서 API 키 관리 (절대 프론트엔드에 노출 금지!)
const API_KEY = process.env.DELIVERY_API_KEY;
const API_SECRET = process.env.DELIVERY_API_SECRET;
const API_URL = 'https://api.deliveryapi.co.kr/v1/tracking/trace';
app.post('/api/tracking', async (req, res) => {
const { courierCode, trackingNumber } = req.body;
// 입력값 검증
if (!courierCode || !trackingNumber) {
return res.status(400).json({
isSuccess: false,
message: '택배사 코드와 송장번호를 입력해주세요.'
});
}
try {
const response = await axios.post(API_URL, {
items: [{ courierCode, trackingNumber }]
}, {
headers: {
'Authorization': `Bearer ${API_KEY}:${API_SECRET}`,
'Content-Type': 'application/json'
}
});
res.json(response.data);
} catch (error) {
res.status(500).json({
isSuccess: false,
message: '조회 중 오류가 발생했습니다.'
});
}
});
app.listen(3000); Next.js API Route로 구현하기
Next.js를 사용하고 있다면 API Route로 더 간단하게 구현할 수 있습니다:
// pages/api/tracking.ts (Next.js)
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method not allowed' });
}
const { courierCode, trackingNumber } = req.body;
const response = await fetch(
'https://api.deliveryapi.co.kr/v1/tracking/trace',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.DELIVERY_API_KEY}:${process.env.DELIVERY_API_SECRET}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
items: [{ courierCode, trackingNumber }]
}),
}
);
const data = await response.json();
res.status(200).json(data);
} 2프론트엔드: 배송조회 UI 만들기
HTML 구조
택배사 선택, 송장번호 입력, 조회 버튼, 결과 영역으로 구성합니다:
<div class="tracking-container">
<h2>배송 조회</h2>
<div class="tracking-form">
<select id="courierSelect">
<option value="">택배사 선택</option>
<option value="cj">CJ대한통운</option>
<option value="lotte">롯데택배</option>
<option value="hanjin">한진택배</option>
<option value="post">우체국택배</option>
<option value="logen">로젠택배</option>
<option value="kyungdong">경동택배</option>
<option value="daesin">대신택배</option>
</select>
<input type="text" id="trackingInput"
placeholder="송장번호를 입력하세요" />
<button id="trackBtn">조회하기</button>
</div>
<!-- 결과 영역 -->
<div id="trackingResult" class="tracking-result"></div>
</div> JavaScript 연동
조회 버튼 클릭 시 백엔드 API를 호출하고 결과를 렌더링합니다:
const trackBtn = document.getElementById('trackBtn');
trackBtn.addEventListener('click', async () => {
const courierCode = document.getElementById('courierSelect').value;
const trackingNumber = document.getElementById('trackingInput').value;
const resultDiv = document.getElementById('trackingResult');
if (!courierCode || !trackingNumber) {
resultDiv.innerHTML = '<p class="error">택배사와 송장번호를 입력해주세요.</p>';
return;
}
// 로딩 표시
resultDiv.innerHTML = '<div class="loading">조회 중...</div>';
try {
const response = await fetch('/api/tracking', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ courierCode, trackingNumber }),
});
const data = await response.json();
if (!data.isSuccess) {
resultDiv.innerHTML = '<p class="error">조회 실패: ' + data.message + '</p>';
return;
}
const result = data.data.results[0].data;
renderTimeline(result, resultDiv);
} catch (error) {
resultDiv.innerHTML = '<p class="error">네트워크 오류가 발생했습니다.</p>';
}
}); 3배송 타임라인 UI 표시하기
배송 진행 상황을 시간순으로 보여주는 타임라인 UI는 고객에게 가장 직관적인 형태입니다.
타임라인 렌더링 함수
function renderTimeline(result, container) {
const statusClass = result.isDelivered ? 'delivered' : 'in-transit';
let html = `
<div class="tracking-summary ${statusClass}">
<h3>${result.courierName} - ${result.deliveryStatusText}</h3>
<p>송장번호: ${result.trackingNumber}</p>
</div>
<div class="timeline">
`;
// progresses를 최신순으로 정렬
const progresses = [...result.progresses].reverse();
progresses.forEach((progress, index) => {
const isLatest = index === 0;
html += `
<div class="timeline-item ${isLatest ? 'latest' : ''}">
<div class="timeline-dot"></div>
<div class="timeline-content">
<span class="timeline-status">${progress.status}</span>
<span class="timeline-location">${progress.location}</span>
<span class="timeline-time">${progress.dateTime}</span>
</div>
</div>
`;
});
html += '</div>';
container.innerHTML = html;
} 타임라인 CSS
.timeline {
position: relative;
padding-left: 30px;
}
.timeline::before {
content: '';
position: absolute;
left: 8px;
top: 0;
bottom: 0;
width: 2px;
background: #e5e7eb;
}
.timeline-item {
position: relative;
padding: 15px 0;
}
.timeline-dot {
position: absolute;
left: -26px;
top: 20px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #d1d5db;
border: 2px solid white;
}
.timeline-item.latest .timeline-dot {
background: #3b82f6;
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2);
}
.tracking-summary.delivered {
background: #dcfce7;
border-left: 4px solid #22c55e;
padding: 16px;
border-radius: 8px;
margin-bottom: 20px;
}
.tracking-summary.in-transit {
background: #dbeafe;
border-left: 4px solid #3b82f6;
padding: 16px;
border-radius: 8px;
margin-bottom: 20px;
} ✅ 결과
배송 완료 시 초록색, 배송 중일 때 파란색으로 표시되는 직관적인 타임라인이 완성됩니다. 최신 배송 상태는 파란 점으로 강조됩니다.
배송 완료 시 초록색, 배송 중일 때 파란색으로 표시되는 직관적인 타임라인이 완성됩니다. 최신 배송 상태는 파란 점으로 강조됩니다.
4에러 처리 & UX 개선
사용자 친화적 에러 메시지
API 에러 코드를 고객이 이해할 수 있는 한글 메시지로 변환합니다:
const ERROR_MESSAGES = {
'INVALID_TRACKING_NUMBER': '올바른 송장번호를 입력해주세요.',
'COURIER_NOT_FOUND': '선택하신 택배사를 확인해주세요.',
'TRACKING_NOT_FOUND': '등록된 배송 정보가 없습니다. 발송 후 1~2일 뒤 다시 조회해주세요.',
'RATE_LIMIT_EXCEEDED': '잠시 후 다시 시도해주세요.',
};
function getErrorMessage(errorCode) {
return ERROR_MESSAGES[errorCode] || '일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요.';
} UX 개선 팁
- 로딩 표시: 조회 버튼 클릭 후 "조회 중..." 표시로 사용자 이탈 방지
- 최근 조회 기록:
localStorage에 마지막 조회 정보를 저장하면 재방문 시 자동 입력 - 자동 새로고침: 배송 중인 건은 30초마다 자동 갱신하면 실시간 느낌 제공
- 모바일 대응: 입력 필드와 타임라인을 모바일에 최적화하세요
핵심 요약
💡 쇼핑몰 배송조회 페이지 구축 4단계
1. 백엔드 프록시 - API 키를 안전하게 관리하는 서버 구축
2. 프론트엔드 폼 - 택배사 선택 + 송장번호 입력 UI
3. 타임라인 UI - 배송 진행 상황을 직관적으로 표시
4. 에러 처리 - 고객 친화적인 메시지로 변환
1. 백엔드 프록시 - API 키를 안전하게 관리하는 서버 구축
2. 프론트엔드 폼 - 택배사 선택 + 송장번호 입력 UI
3. 타임라인 UI - 배송 진행 상황을 직관적으로 표시
4. 에러 처리 - 고객 친화적인 메시지로 변환
다음 단계
- 택배 API 5분 만에 연동하기 - API 키 발급부터 시작
- 웹훅으로 실시간 배송 알림 받기 - 배송 상태 변경 시 자동 알림
- API 에러 처리 베스트 프랙티스 - 안정적인 서비스 구축
- 전체 API 문서 - 상세한 API 레퍼런스