브랜드 추적 페이지 구축하기

고객이 택배사의 추적 링크를 클릭할 때마다 여러분의 사이트를 떠나게 됩니다. 이는 인게이지먼트, 브랜딩, 고객 지원 부담 감소의 기회를 놓치는 것입니다. 브랜드 추적 페이지는 고객을 여러분의 도메인에 머물게 하면서 더 나은 경험을 제공합니다. 지금부터 함께 만들어 보겠습니다.

왜 브랜드 추적 페이지가 필요한가요?

택배사 추적 페이지의 한계

고객을 택배사의 추적 페이지로 보내면 다음과 같은 문제가 생깁니다:

  • 고객이 여러분의 브랜드 경험을 벗어나게 됩니다
  • 고객이 보는 것은 여러분의 프로모션이 아닌 택배사의 광고입니다
  • 고객이 여러분의 지원팀에 쉽게 연락할 수 없습니다
  • 추적 정보가 혼란스러우면 택배사가 아닌 여러분 탓을 합니다

브랜드 추적 페이지의 효과

효과기대 수치
고객 사이트 이탈 방지재방문 페이지뷰 +15%
CS 문의 감소배송 추적 문의 -40%
추가 매출 기회관련 상품 추천 노출
브랜드 일관성전문적인 구매 후 경험
데이터 수집고객 인게이지먼트 분석

아키텍처 개요

Customer clicks tracking link

Your branded tracking page

JavaScript fetches tracking data from your API

Your API calls WhereParcel → Returns standardized data

Render tracking timeline + brand elements

1단계: 백엔드 API 엔드포인트

WhereParcel 요청을 프록시하는 API 엔드포인트를 생성합니다:

// routes/tracking.js
const express = require('express');
const router = express.Router();

router.get('/api/track/:trackingNumber', async (req, res) => {
  const { trackingNumber } = req.params;

  try {
    // Look up order by tracking number
    const order = await db.orders.findByTracking(trackingNumber);

    // Fetch tracking data from WhereParcel
    const tracking = await fetch('https://api.whereparcel.com/v2/track', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.WHEREPARCEL_API_KEY}:${process.env.WHEREPARCEL_SECRET_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        trackingItems: [{ carrier: order?.carrier || 'auto', trackingNumber }],
      }),
    }).then(r => r.json());

    res.json({
      order: order ? {
        id: order.id,
        items: order.items.map(i => ({
          name: i.name,
          image: i.imageUrl,
          quantity: i.quantity,
        })),
      } : null,
      tracking: tracking.data,
    });
  } catch (error) {
    res.status(500).json({ error: 'Unable to fetch tracking data' });
  }
});

module.exports = router;

2단계: 추적 페이지 HTML 구조

추적 페이지의 기본 구조를 만듭니다:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Track Your Order | YourStore</title>
  <style>
    /* See Step 4 for full styles */
  </style>
</head>
<body>
  <header class="tracking-header">
    <a href="/" class="logo">
      <img src="/logo.svg" alt="YourStore" />
    </a>
    <a href="/support" class="support-link">Need help?</a>
  </header>

  <main class="tracking-container">
    <!-- Search form (if no tracking number in URL) -->
    <div id="search-section" class="search-section">
      <h1>Track Your Order</h1>
      <form id="tracking-form">
        <input
          type="text"
          id="tracking-input"
          placeholder="Enter your tracking number"
          required
        />
        <button type="submit">Track</button>
      </form>
    </div>

    <!-- Results -->
    <div id="results-section" class="results-section" style="display:none">
      <div id="status-banner" class="status-banner"></div>
      <div id="order-items" class="order-items"></div>
      <div id="tracking-timeline" class="tracking-timeline"></div>
    </div>

    <!-- Loading state -->
    <div id="loading" class="loading" style="display:none">
      <div class="spinner"></div>
      <p>Fetching tracking information...</p>
    </div>
  </main>

  <footer class="tracking-footer">
    <p>&copy; 2026 YourStore. All rights reserved.</p>
  </footer>

  <script src="/tracking.js"></script>
</body>
</html>

3단계: 프론트엔드 JavaScript

추적 데이터를 가져와서 렌더링합니다:

// public/tracking.js

const STATUS_CONFIG = {
  pending: { label: 'Pending', icon: 'clock', color: '#6b7280' },
  info_received: { label: 'Info Received', icon: 'file', color: '#3b82f6' },
  picked_up: { label: 'Picked Up', icon: 'package', color: '#3b82f6' },
  in_transit: { label: 'In Transit', icon: 'truck', color: '#f59e0b' },
  out_for_delivery: { label: 'Out for Delivery', icon: 'map-pin', color: '#8b5cf6' },
  delivered: { label: 'Delivered', icon: 'check-circle', color: '#10b981' },
  exception: { label: 'Exception', icon: 'alert', color: '#ef4444' },
};

// Check URL for tracking number
const urlParams = new URLSearchParams(window.location.search);
const trackingNumber = urlParams.get('tn');

if (trackingNumber) {
  document.getElementById('tracking-input').value = trackingNumber;
  fetchTracking(trackingNumber);
}

// Form submission
document.getElementById('tracking-form').addEventListener('submit', (e) => {
  e.preventDefault();
  const tn = document.getElementById('tracking-input').value.trim();
  if (tn) {
    // Update URL without reload
    window.history.pushState({}, '', `?tn=${tn}`);
    fetchTracking(tn);
  }
});

async function fetchTracking(trackingNumber) {
  const loading = document.getElementById('loading');
  const results = document.getElementById('results-section');
  const search = document.getElementById('search-section');

  loading.style.display = 'flex';
  results.style.display = 'none';

  try {
    const response = await fetch(`/api/track/${trackingNumber}`);
    const data = await response.json();

    if (!response.ok) throw new Error(data.error);

    renderResults(data);
    results.style.display = 'block';
  } catch (error) {
    results.innerHTML = `
      <div class="error-message">
        <h2>Unable to find tracking information</h2>
        <p>Please check your tracking number and try again.</p>
      </div>
    `;
    results.style.display = 'block';
  } finally {
    loading.style.display = 'none';
  }
}

function renderResults(data) {
  const { tracking, order } = data;
  const config = STATUS_CONFIG[tracking.status] || STATUS_CONFIG.pending;

  // Status banner
  document.getElementById('status-banner').innerHTML = `
    <div class="status-icon" style="background: ${config.color}20; color: ${config.color}">
      ${config.label}
    </div>
    <h2>${getStatusMessage(tracking.status)}</h2>
    ${tracking.estimatedDelivery
      ? `<p class="eta">Estimated delivery: ${formatDate(tracking.estimatedDelivery)}</p>`
      : ''}
  `;

  // Order items (if available)
  if (order?.items?.length) {
    document.getElementById('order-items').innerHTML = `
      <h3>Your Items</h3>
      <div class="items-grid">
        ${order.items.map(item => `
          <div class="item-card">
            <img src="${item.image}" alt="${item.name}" />
            <span>${item.name}</span>
            <span class="qty">x${item.quantity}</span>
          </div>
        `).join('')}
      </div>
    `;
  }

  // Timeline
  document.getElementById('tracking-timeline').innerHTML = `
    <h3>Tracking History</h3>
    <div class="timeline">
      ${tracking.events.map((event, i) => `
        <div class="timeline-event ${i === 0 ? 'latest' : ''}">
          <div class="timeline-dot"></div>
          <div class="timeline-content">
            <div class="event-time">${formatDateTime(event.timestamp)}</div>
            <div class="event-status">${event.description}</div>
            ${event.location ? `<div class="event-location">${event.location}</div>` : ''}
          </div>
        </div>
      `).join('')}
    </div>
  `;
}

function getStatusMessage(status) {
  const messages = {
    pending: 'Your order is being prepared',
    picked_up: 'Your package has been picked up',
    in_transit: 'Your package is on its way',
    out_for_delivery: 'Your package is out for delivery today!',
    delivered: 'Your package has been delivered',
    exception: 'There is an issue with your delivery',
  };
  return messages[status] || 'Tracking your package';
}

function formatDate(dateStr) {
  return new Date(dateStr).toLocaleDateString('en-US', {
    weekday: 'long', month: 'long', day: 'numeric',
  });
}

function formatDateTime(dateStr) {
  return new Date(dateStr).toLocaleString('en-US', {
    month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit',
  });
}

4단계: CSS 스타일링

브랜드에 맞게 스타일을 적용합니다:

:root {
  --brand-primary: #4f46e5;
  --brand-secondary: #818cf8;
  --text-primary: #1f2937;
  --text-secondary: #6b7280;
  --bg-primary: #ffffff;
  --bg-secondary: #f9fafb;
  --border: #e5e7eb;
}

.tracking-container {
  max-width: 640px;
  margin: 0 auto;
  padding: 2rem 1rem;
}

.status-banner {
  text-align: center;
  padding: 2rem;
  background: var(--bg-secondary);
  border-radius: 12px;
  margin-bottom: 2rem;
}

.status-icon {
  display: inline-block;
  padding: 0.5rem 1rem;
  border-radius: 9999px;
  font-weight: 600;
  font-size: 0.875rem;
  margin-bottom: 0.75rem;
}

.timeline {
  position: relative;
  padding-left: 2rem;
}

.timeline::before {
  content: '';
  position: absolute;
  left: 7px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--border);
}

.timeline-event {
  position: relative;
  padding-bottom: 1.5rem;
}

.timeline-dot {
  position: absolute;
  left: -2rem;
  top: 4px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--border);
  border: 3px solid var(--bg-primary);
}

.timeline-event.latest .timeline-dot {
  background: var(--brand-primary);
}

.event-time {
  font-size: 0.8125rem;
  color: var(--text-secondary);
}

.event-status {
  font-weight: 500;
  color: var(--text-primary);
}

.event-location {
  font-size: 0.875rem;
  color: var(--text-secondary);
}

5단계: 인게이지먼트 요소 추가

추적 페이지의 가치를 극대화합니다.

추천 상품 노출

function renderRecommendations(order) {
  if (!order) return;

  return `
    <section class="recommendations">
      <h3>You might also like</h3>
      <div class="product-grid">
        ${getRelatedProducts(order.items).map(product => `
          <a href="${product.url}" class="product-card">
            <img src="${product.image}" alt="${product.name}" />
            <span class="product-name">${product.name}</span>
            <span class="product-price">${product.price}</span>
          </a>
        `).join('')}
      </div>
    </section>
  `;
}

고객이 배송을 기다리는 동안 관련 상품을 추천하면 자연스러운 추가 매출 기회가 됩니다. 구매한 상품과 연관된 액세서리, 소모품, 또는 인기 상품을 노출하는 것이 효과적입니다.

배송 완료 후 피드백 수집

배송이 완료되면 배송 경험에 대한 피드백을 요청합니다:

if (tracking.status === 'delivered') {
  resultsHTML += `
    <section class="feedback-section">
      <h3>How was your delivery experience?</h3>
      <div class="rating-buttons">
        <button onclick="submitRating(5)">Excellent</button>
        <button onclick="submitRating(4)">Good</button>
        <button onclick="submitRating(3)">Average</button>
        <button onclick="submitRating(2)">Poor</button>
      </div>
    </section>
  `;
}

이렇게 수집한 피드백 데이터는 택배사별 배송 품질을 평가하고, 택배사 선택 전략을 수립하는 데 귀중한 자료가 됩니다. 또한 부정적인 피드백이 접수되면 CS팀이 선제적으로 대응할 수 있어 고객 만족도를 높이는 데 도움이 됩니다.

SEO 이점

브랜드 추적 페이지는 SEO 관점에서도 유리합니다:

  • 사이트 체류 시간 증가 — 고객이 추적 페이지를 여러 번 방문합니다
  • 이탈률 감소 — 고객이 여러분의 도메인 내에 머무릅니다
  • 내부 링크 — 상품, 블로그, 지원 페이지로의 연결이 가능합니다
  • 브랜드 권위 — 전문적인 추적 경험이 신뢰를 구축합니다

마무리

브랜드 추적 페이지는 구축할 수 있는 프로젝트 중 ROI가 가장 높은 것 중 하나입니다. CS 문의를 줄이고, 고객 인게이지먼트를 유지하며, 추가 매출 기회를 만들고, 전문적인 구매 후 경험을 제공합니다. WhereParcel의 API가 추적 데이터를 제공하므로 여러분은 프론트엔드 경험에만 집중하시면 됩니다.

참고 자료: