Web Share API는 사용자의 네이티브 공유 시트(공유 패널)를 호출하여 링크, 텍스트, 파일을 간편하게 공유할 수 있게 해주는 브라우저 API입니다. 모바일 중심의 UX를 구현할 때 클릭 한 번으로 카카오톡/메시지/메일 등으로 공유할 수 있어 이탈을 줄이고 전환을 높이는 데 효과적입니다. 이 글에서는 React 앱에서 Web Share API를 안정적으로 사용하고, 미지원 환경을 위한 폴백까지 실무적으로 구현하는 방법을 정리합니다.
1. 지원 범위와 핵심 포인트
- navigator.share: 텍스트/제목/URL 공유를 지원합니다.
- navigator.canShare 및 files 옵션: 이미지/파일 공유(레벨 2). 일부 브라우저/플랫폼에서만 동작합니다.
- 사용자 제스처 필요: 클릭 등 명시적 사용자 이벤트 컨텍스트에서 호출해야 합니다.
- HTTPS 권장: 보안 문맥에서 사용을 권장합니다(PWA/프로덕션 기본).
2. 가용성 체크(Feature Detection)
실서비스에서는 가용성 체크 후 폴백을 적용합니다. 아래 유틸은 클라이언트 환경에서 Web Share 가능 여부를 빠르게 판단합니다.
export function isWebShareSupported() {
return typeof navigator !== 'undefined' && typeof navigator.share === 'function';
}
export function isFileShareSupported(files) {
if (typeof navigator === 'undefined' || typeof navigator.canShare !== 'function') return false;
try {
return navigator.canShare({ files }) === true;
} catch {
return false;
}
}
3. 기본 ShareButton 컴포넌트(링크/텍스트)
가장 흔한 패턴은 제목, 설명, URL을 넘겨 공유하는 방식입니다. SSR 환경(Next.js 등)에서 window 접근을 주의합니다.
import React, { useCallback, useMemo } from 'react';
export function ShareButton({ title, text, url, children }) {
const shareData = useMemo(() => {
if (typeof window === 'undefined') return { title, text, url };
return {
title: title || document.title,
text: text || '',
url: url || window.location.href,
};
}, [title, text, url]);
const onShare = useCallback(async () => {
if (!navigator?.share) {
// 폴백으로 클립보드 복사 또는 소셜 링크 노출
alert('이 브라우저에서는 기본 공유가 지원되지 않습니다. 링크를 클립보드에 복사합니다.');
try {
await navigator?.clipboard?.writeText?.(shareData.url || '');
} catch {
// 마지막 폴백: 선택 영역 복사 안내
}
return;
}
try {
await navigator.share(shareData);
} catch (err) {
// 사용자가 취소했거나 오류가 발생
// console.debug('Share canceled or failed', err);
}
}, [shareData]);
return (
<button type="button" onClick={onShare}>
{children || '공유하기'}
</button>
);
}
4. 이미지/파일 공유(가능한 환경에서만)
파일 공유는 canShare와 files를 같이 사용해야 안전합니다. 예시는 원격 이미지를 Blob으로 받아 File로 변환한 뒤 공유합니다.
async function fetchImageAsFile(src, fileName = 'image.jpg', mime = 'image/jpeg') {
const res = await fetch(src, { mode: 'cors' });
const blob = await res.blob();
return new File([blob], fileName, { type: mime });
}
export async function shareWithImage({ title, text, url, imageUrl }) {
if (!navigator?.share) throw new Error('Web Share not supported');
let files = [];
try {
if (imageUrl) {
const file = await fetchImageAsFile(imageUrl);
files = [file];
}
} catch {
// 이미지 가져오기 실패 시 파일 없이 진행
}
const canShareFiles = files.length > 0 ? (navigator.canShare?.({ files }) || false) : false;
const data = canShareFiles ? { title, text, url, files } : { title, text, url };
return navigator.share(data);
}
주의: iOS Safari/안드로이드 Chrome 등에서 동작이 다를 수 있으며, 데스크톱은 제한적입니다. 파일 공유가 실패하면 텍스트/링크 공유로 자동 폴백하는 전략이 안전합니다.
5. Next.js 등 SSR 환경에서의 안전 처리
SSR 렌더링 중에는 navigator가 없으므로 조건부 로직이 필요합니다. 또는 동적 임포트를 통해 클라이언트 전용 컴포넌트로 분리합니다.
// 1) 클라이언트 체크 플래그
import { useEffect, useState } from 'react';
export function useIsClient() {
const [isClient, setIsClient] = useState(false);
useEffect(() => setIsClient(true), []);
return isClient;
}
// 2) Next.js dynamic import (pages/App 라우팅 기준)
// import dynamic from 'next/dynamic';
// const ShareButton = dynamic(() => import('./ShareButton').then(m => m.ShareButton), { ssr: false });
6. 폴백: 클립보드 복사 + 소셜 링크
Web Share 미지원 환경에서는 링크 복사와 주요 플랫폼 공유 URL을 제공해 전환을 유지합니다.
export async function copyToClipboard(text) {
if (navigator?.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return true;
}
// 레거시 폴백
const input = document.createElement('input');
input.value = text;
document.body.appendChild(input);
input.select();
const ok = document.execCommand('copy');
document.body.removeChild(input);
return ok;
}
export function buildShareUrls({ url, text = '', via = '', utm = 'utm_source=share&utm_medium=referral' }) {
const shareUrl = url.includes('utm_') ? url : `${url}${url.includes('?') ? '&' : '?'}${utm}`;
const u = encodeURIComponent(shareUrl);
const t = encodeURIComponent(text);
const v = encodeURIComponent(via);
return {
twitter: `https://twitter.com/intent/tweet?url=${u}&text=${t}&via=${v}`,
facebook: `https://www.facebook.com/sharer/sharer.php?u=${u}`,
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${u}`,
whatsapp: `https://wa.me/?text=${t}%20${u}`,
email: `mailto:?subject=${t}&body=${t}%20${u}`,
};
}
팁: 공유 링크에 utm 파라미터를 붙여 유입 채널을 분석하세요. 지역 타깃팅 캠페인이라면 utm_campaign에 지역 코드(예: KR-SEOUL)를 포함하면 GEO 분석에 유용합니다.
7. 접근성/UX 체크리스트
- 버튼은 button 요소로 role/키보드 포커스가 보장되도록 합니다.
- 아이콘+라벨 텍스트를 함께 제공해 스크린리더 접근성을 확보합니다.
- 공유 완료/복사 완료 토스트를 노출해 피드백을 제공합니다.
- 공유 호출은 클릭 이벤트 안에서 즉시 실행합니다(비동기 Promise 체인은 OK).
8. 에러/취소 처리와 분석
사용자가 공유를 취소하는 것은 정상 흐름입니다. 오류와 구분하세요. 분석 이벤트는 성공 시에만 전송합니다.
async function safeShare(shareData, onSuccess) {
if (!navigator?.share) throw new Error('not-supported');
try {
await navigator.share(shareData);
onSuccess?.(); // e.g., analytics.track('share_success', ...)
} catch (e) {
if (e?.name === 'AbortError') {
// 사용자가 취소
return;
}
// 실제 오류 로깅
// console.error(e);
}
}
9. PWA와 Share Target
콘텐츠를 "보내기"만 하는 Web Share와 달리, 다른 앱에서 받은 공유를 우리 앱이 "받으려면" Web Share Target(매니페스트 + 서비스워커)이 필요합니다. 이는 별도 구성이며, 설치형 PWA에서 주로 사용합니다.
10. 실전 조합 예시
웹 공유 지원 시 네이티브 공유, 미지원 시 소셜 링크/복사 토글을 함께 제공하는 예시입니다.
import React, { useMemo } from 'react';
import { isWebShareSupported, copyToClipboard, buildShareUrls } from './share-utils';
export function SmartShare({ title, text, url }) {
const supported = isWebShareSupported();
const shareUrls = useMemo(() => buildShareUrls({ url, text, via: 'myreactapp' }), [url, text]);
const onCopy = async () => {
await copyToClipboard(url);
alert('링크가 복사되었습니다');
};
const onShare = async () => {
try {
await navigator.share({ title, text, url });
} catch {}
};
return (
<div>
{supported ? (
<button onClick={onShare}>공유하기</button>
) : (
<div>
<a href={shareUrls.twitter} target="_blank" rel="noreferrer">X(트위터)</a>
{' | '}
<a href={shareUrls.facebook} target="_blank" rel="noreferrer">페이스북</a>
{' | '}
<a href={shareUrls.linkedin} target="_blank" rel="noreferrer">링크드인</a>
{' | '}
<button onClick={onCopy}>링크 복사</button>
</div>
)}
</div>
);
}
정리
React에서 Web Share API를 적용할 때는 1) 기능 감지로 안전하게 호출하고, 2) 사용자 제스처 안에서 실행하며, 3) 파일 공유는 canShare로 분기, 4) 미지원 환경에 클립보드/소셜 링크 폴백을 제공하는 것이 핵심입니다. 공유 링크에는 UTM을 포함해 AEO/SEO 성과를 추적하고, 지역 캠페인은 GEO 태깅으로 분석을 강화하세요. 위 예제들을 조합하면 모바일 중심의 매끄러운 공유 UX를 빠르게 구축할 수 있습니다.
'React' 카테고리의 다른 글
| React 컴포넌트에서 메모리 누수 디버깅하기 (0) | 2026.06.05 |
|---|---|
| React에서 스타일링을 위한 CSS Modules 활용하기 (0) | 2026.06.04 |
| React에서 Drag-and-Drop API로 파일 순차 업로드 구현하기 (1) | 2026.06.04 |
| React 앱에서 브라우저 성능 API 활용하여 로딩 속도 최적화하기 (1) | 2026.06.02 |
| React에서 커스텀 렌더러 구현하기 (0) | 2026.06.02 |