728x90
안녕하세요 오늘은 React 프로젝트 카드나 블로그 게시글 리스트에 간단한 공유 버튼(공유 or 복사) 을 추가하는 방법을 소개해보려고 합니다.
공유 버튼 사용 이유
블로그 글이나 포트폴리오 프로젝트는 결국 누군가에게 보여주려고 작성하는 글입니다. 링크를 쉽게 공유할 수 있는 버튼 하나만 달아줘도 독자 입장에서는 주소창 열어서 복사/붙여넣기 하는 수고를 덜 수 있습니다. 작은 디테일이지만 체감 UX가 확 달라집니다
요즘은 모바일에서 글을 보는 경우가 많습니다. 주소창 열어서 링크 복사하기는 은근 번거로운데, 공유 버튼을 누르면 바로 카톡/메일/sns 로 바로 보낼 수 있습니다. 네이티브 공유 시트가 뜨기 때문에 “이거 공유해야겠다” 싶은 순간 바로 행동으로 이어집니다.
- 모바일: 네이티브 공유 시트(navigator.share)가 열려서 카톡/메일 등으로 바로 전송 가능
- 데스크탑: 지원 안 되는 경우 자동으로 링크를 클립보드 복사
사용한 브라우저 API & 라이브러리
- navigator.share
- 모바일 브라우저(특히 iOS Safari, Android Chrome)에서 네이티브 공유 시트를 띄워줍니다.
- 단, 모든 브라우저에서 지원하는 건 아니기 때문에 지원하지 않는 경우를 대비한 대체 로직이 필요합니다.
- navigator.clipboard
- HTTPS 환경에서 안전하게 클립보드 복사를 지원합니다
- react-hot-toast
- 복사 완료 시 피드백을 깔끔하게 보여주기 위해 사용했습니다.
- alert() 대신 토스트 알림을 쓰면 UI가 훨씬 자연스럽습니다.
1. share button 컴포넌트
import { toast } from "react-hot-toast"
export default function ShareButton({ url, title }) {
const canWebShare = typeof navigator !== "undefined" && !!navigator.share
const shareUrl = url || window.location.href
const copyFallback = async () => {
try {
await navigator.clipboard.writeText(shareUrl)
toast.success("링크를 복사했어요!")
} catch {
// 구식 브라우저 폴백
const ta = document.createElement("textarea")
ta.value = shareUrl
document.body.appendChild(ta)
ta.select()
document.execCommand("copy")
ta.remove()
toast.success("링크를 복사했어요!")
}
}
const onClick = async () => {
if (canWebShare) {
try {
await navigator.share({ url: shareUrl, title })
} catch {
// 사용자가 공유 취소 시 무시
}
} else {
await copyFallback()
}
}
return (
<button
onClick={onClick}
className="ml-3 rounded border px-2 py-1 text-xs hover:bg-gray-50
dark:hover:bg-zinc-800 inline-flex items-center gap-1"
>
🔗 공유
</button>
)
}
이제 위 버튼을 공유하고자 하는 게시글 or 프로젝트 카드 리스트에 적용해줍니다.
이 게시글에서는 기존 프로젝트 목록을 보여주는 ui 에 공유 버튼을 추가하고자합니다.
2. 기존 프로젝트 목록
## 프로젝트 목록
function ProjectGallery() {
const [activeFilter, setActiveFilter] = useState('all')
const projects = [
{
id: 1,
title: "E-Commerce Platform",
category: "web",
image: "https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=400",
description: "Full-stack e-commerce platform with React and Node.js",
tech: ["React", "Node.js", "MongoDB", "Stripe"],
link: "#"
},
{
id: 2,
title: "Task Management App",
category: "mobile",
image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=400",
description: "Cross-platform mobile app for task management",
tech: ["React Native", "Firebase", "Redux"],
link: "#"
},
{
id: 3,
title: "Portfolio Website",
category: "web",
image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=400",
description: "Modern portfolio website with animations",
tech: ["React", "Framer Motion", "Tailwind CSS"],
link: "#"
},
{
id: 4,
title: "Weather Dashboard",
category: "web",
image: "https://images.unsplash.com/photo-1592210454359-9043f067919b?w=400",
description: "Real-time weather dashboard with charts",
tech: ["Vue.js", "Chart.js", "OpenWeather API"],
link: "#"
},
{
id: 5,
title: "Fitness Tracker",
category: "mobile",
image: "https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=400",
description: "Mobile app for tracking workouts and nutrition",
tech: ["Flutter", "SQLite", "Google Fit API"],
link: "#"
},
{
id: 6,
title: "Social Media Dashboard",
category: "web",
image: "https://images.unsplash.com/photo-1611162617213-7d7a39e9b1d7?w=400",
description: "Analytics dashboard for social media management",
tech: ["Angular", "D3.js", "Express.js"],
link: "#"
}
]
## 프로젝트 ui
return (
<section className="py-20 bg-gray-50">
<div className="max-w-7xl mx-auto px-6">
{/* 헤더 */}
<div className="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900 mb-4">
My Projects
</h2>
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
Explore my latest work across web development, mobile apps, and creative solutions.
</p>
</div>
{/* 필터 버튼 */}
<div className="flex justify-center mb-8 space-x-4">
{['all', 'web', 'mobile'].map(filter => (
<button
key={filter}
onClick={() => setActiveFilter(filter)}
className={`px-6 py-2 rounded-full font-medium transition-colors ${
activeFilter === filter
? 'bg-blue-600 text-white'
: 'bg-white text-gray-600 hover:bg-gray-100'
}`}
>
{filter === 'all' ? 'All Projects' :
filter === 'web' ? 'Web Apps' : 'Mobile Apps'}
</button>
))}
</div>
{/* 프로젝트 그리드 */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{filteredProjects.map(project => (
<div
key={project.id}
className="bg-white rounded-xl shadow-lg overflow-hidden hover:shadow-xl transition-shadow duration-300"
>
{/* 이미지 */}
<div className="relative h-48 overflow-hidden">
<img
src={project.image}
alt={project.title}
className="w-full h-full object-cover hover:scale-110 transition-transform duration-300"
/>
<div className="absolute top-4 right-4">
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
project.category === 'web'
? 'bg-blue-100 text-blue-800'
: 'bg-green-100 text-green-800'
}`}>
{project.category === 'web' ? 'Web' : 'Mobile'}
</span>
</div>
</div>
{/* 내용 */}
<div className="p-6">
<h3 className="text-xl font-bold text-gray-900 mb-2">
{project.title}
</h3>
<p className="text-gray-600 mb-4">
{project.description}
</p>
{/* 기술 스택 */}
<div className="flex flex-wrap gap-2 mb-4">
{project.tech.map(tech => (
<span
key={tech}
className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded"
>
{tech}
</span>
))}
</div>
{/* 링크 버튼 */}
<a
href={project.link}
className="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium"
>
View Project
<svg className="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</a>
<ShareButton slug={project.id} title={project.title} />
</div>
</div>
))}
</div>
</div>
</section>
)
}
export default ProjectGallery
3. 링크 공유 버튼 추가하기
return 내부에 있는 ui 가장 하단 부분에
shrare button 을 추가해줍니다.
<ShareButton slug={project.id} title={project.title} />
프로젝트마다 공유버튼이 생성된 것을 확인할 수 있습니다.
'React' 카테고리의 다른 글
[React] 바깥 클릭 시 닫히는 드롭다운 (useOnClickOutside 훅) (1) | 2025.09.03 |
---|---|
[React] Tailwind v4로 웹페이지 다크모드 설정하기 (클래스 기반) (0) | 2025.09.02 |
[React] LottieFiles 애니메이션 적용하기 (0) | 2025.08.21 |
[React] 3D 아바타 컴포넌트 만들기 (0) | 2025.08.11 |
[React] 웹페이지 메뉴바 드롭다운 만들기 (0) | 2025.08.06 |