QR 코드는 로그인, 결제, 딥링크 공유 등 다양한 UX를 빠르게 개선할 수 있는 도구입니다. 이 글에서는 React 앱에 QR 코드 생성과 스캐닝(카메라 인식) 기능을 간단히 붙이는 실무 가이드를 제공합니다.
1. 라이브러리 선택
생성(Generate): react-qr-code 또는 qrcode.react를 권장합니다. 둘 다 가볍고 SVG 출력이 깔끔합니다. 여기서는 react-qr-code를 사용합니다.
스캔(Scan): react-qr-reader가 접근성이 좋고 구현이 간단합니다. 모바일 브라우저 호환도 양호합니다.
2. 설치
// npm
npm i react-qr-code react-qr-reader
// yarn
yarn add react-qr-code react-qr-reader
// pnpm
pnpm add react-qr-code react-qr-reader
3. QR 코드 생성 컴포넌트
입력한 텍스트/URL을 즉시 QR 코드로 렌더링하고, PNG로 저장하는 예시입니다(SVG를 Canvas로 변환).
import { useState, useRef } from 'react';
import QRCode from 'react-qr-code';
export default function QrGenerator() {
const [value, setValue] = useState('https://example.com');
const wrapperRef = useRef(null);
const downloadPng = () => {
const svg = wrapperRef.current?.querySelector('svg');
if (!svg) return;
const serializer = new XMLSerializer();
const svgStr = serializer.serializeToString(svg);
const img = new Image();
const svgBlob = new Blob([svgStr], { type: 'image/svg+xml;charset=utf-8' });
const url = URL.createObjectURL(svgBlob);
img.onload = () => {
const size = 512; // 내보낼 해상도
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, size, size);
ctx.drawImage(img, 0, 0, size, size);
const a = document.createElement('a');
a.download = 'qr.png';
a.href = canvas.toDataURL('image/png');
a.click();
URL.revokeObjectURL(url);
};
img.src = url;
};
return (
<div style={{ maxWidth: 320 }}>
<label>
인코딩할 텍스트/URL
<input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="https://your.app"
style={{ width: '100%', marginTop: 8 }}
/>
</label>
<div ref={wrapperRef} style={{ background: 'white', padding: 12, marginTop: 12 }}>
<QRCode
value={value || ' '}
size={256}
level="M"
fgColor="#111"
bgColor="#ffffff"
/>
</div>
<button onClick={downloadPng} style={{ marginTop: 8 }}>
PNG로 다운로드
</button>
</div>
);
}
팁: QR 데이터가 비어 있으면 스캐너가 실패할 수 있으므로 빈 문자열 대신 공백을 넣습니다(value || ' '). 브랜드 컬러 대비가 낮으면 인식률이 떨어지므로 배경은 밝게, 전경은 어둡게 유지합니다.
4. QR 코드 스캐너 컴포넌트
카메라로 QR 코드를 읽고 결과를 일시정지하는 예시입니다. HTTPS 환경에서 테스트하세요.
import { useRef, useState } from 'react';
import { QrReader } from 'react-qr-reader';
export default function QrScanner() {
const [data, setData] = useState('');
const [paused, setPaused] = useState(false);
const lastAtRef = useRef(0);
const onResult = (result, error) => {
if (result) {
const text = result.getText ? result.getText() : result?.text;
const now = Date.now();
// 과도한 콜백 방지(0.8초 스로틀)
if (now - lastAtRef.current < 800) return;
lastAtRef.current = now;
if (text) {
setData(text);
setPaused(true); // 자동 일시정지로 중복 실행 방지
}
}
// error는 필요 시 로깅 정도로 처리합니다.
};
return (
<div>
<div style={{ aspectRatio: '1 / 1', maxWidth: 360, position: 'relative' }}>
{!paused && (
<QrReader
constraints={{ facingMode: 'environment' }}
onResult={onResult}
videoStyle={{ width: '100%', borderRadius: 12 }}
containerStyle={{ width: '100%' }}
/>
)}
{/* 간단한 조준 가이드 오버레이 */}
<div style={{
position: 'absolute', inset: 0, pointerEvents: 'none',
boxShadow: 'inset 0 0 0 2px rgba(255,255,255,0.7)'
}} />
</div>
<p>인식 결과: {data || '대기 중...'}</p>
<div style={{ display: 'flex', gap: 8 }}>
<button onClick={() => setPaused((p) => !p)}>
{paused ? '스캔 재개' : '스캔 일시정지'}
</button>
{data && (/^https?:\/\//.test(data) ? (
<a href={data} target="_blank" rel="noreferrer">링크 열기</a>
) : null)}
</div>
</div>
);
}
보안 팁: 스캔 결과를 자동으로 이동시키지 말고, 사용자 확인 후 이동하도록 합니다. 악성 QR로 인한 피싱을 방지합니다.
5. 권한, HTTPS, 호환성 체크
카메라 접근은 보안 컨텍스트(HTTPS 또는 localhost)에서만 동작합니다. iOS Safari는 파일 스킴/HTTP에서 카메라 접근이 차단됩니다.
// 카메라 사용 가능 여부 사전 점검 (선택)
export async function canUseCamera() {
if (!navigator.mediaDevices?.getUserMedia) return false;
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
stream.getTracks().forEach(t => t.stop());
return true;
} catch (e) {
return false;
}
}
SSR(Next.js 등)에서는 스캐너를 클라이언트에서만 로드하세요.
// Next.js 예시: 동적 임포트로 SSR 방지
import dynamic from 'next/dynamic';
const SafeQrScanner = dynamic(() => import('./QrScanner'), { ssr: false });
export default function Page() {
return <SafeQrScanner />;
}
6. UX/성능 최적화 팁
1) 스캔 상태 관리: 결과가 나올 때 자동으로 일시정지 후, 버튼으로 재개하면 중복 이벤트를 방지합니다. 2) 조도 환경 안내: 어두운 환경에서는 플래시를 켜 달라는 안내를 합니다(Android 일부는 torch 제어 가능). 3) 프레임 제한: 스로틀/디바운스로 onResult 호출을 제한합니다. 4) 폴백 제공: 카메라가 없는 디바이스에는 이미지 업로드로 QR 인식을 제공하는 UX를 고려합니다(zxing/browser 등 활용). 5) 접근성: 중요한 버튼에는 role/label을 명확히 지정하고, 스캔 결과를 라이브 리전에 읽히도록 설정하면 좋습니다.
7. 디버깅 체크리스트
- 브라우저 콘솔에 NotAllowedError: 권한 거부입니다. 설정에서 카메라 허용 또는 페이지를 HTTPS로 서빙합니다. - NotFoundError: 카메라가 없거나 다른 앱이 사용 중입니다. - iOS에서 빈 화면: PWA standalone + iOS에서는 일부 제약이 있으므로 최신 iOS 및 HTTPS에서 테스트합니다. - 인식률 낮음: 대비 부족, 너무 작은 코드, 모션 블러가 원인입니다. QR 크기를 키우고 조명을 개선합니다.
8. 배포 전 점검
1) 배포 URL이 HTTPS인지 확인합니다. 2) 개인정보 처리 고지: 카메라 프리뷰는 인식 용도로만 사용한다고 명시합니다. 3) Lighthouse로 모바일 성능과 접근성을 점검합니다. 4) 다양한 기기(안드로이드/아이폰, 크롬/사파리)에서 실기 테스트를 수행합니다.
위 구성만으로도 대부분의 서비스에 즉시 적용 가능한 QR 생성/스캔 기능을 제공할 수 있습니다. 비즈니스 플로우에 맞춰 결과 처리 로직만 연결하면 끝입니다.
'React' 카테고리의 다른 글
| React에서 스크롤 위치 저장 및 복원 기능 구현하기 (1) | 2026.06.10 |
|---|---|
| React 앱에 서버리스 함수(Function) 연동하기 (0) | 2026.06.05 |
| React에서 Chart.js를 사용한 동적 차트 생성 (0) | 2026.06.05 |
| React 컴포넌트에서 메모리 누수 디버깅하기 (0) | 2026.06.05 |
| React에서 스타일링을 위한 CSS Modules 활용하기 (0) | 2026.06.04 |