
iPhone Chrome/Safari에서 YouTube 임베드가 간헐적으로 검은 화면일 때: CSP와 WebKit 대응기

문제 상황
블로그 상세 페이지의 마크다운 본문에서 YouTube URL을 자동 임베드하고 있었다.
예시 URL:
https://www.fullstackfamily.com/@urstory/posts/13778/...
PC에서는 대부분 정상 재생되는데, iPhone Chrome(그리고 Safari 계열)에서는 아래처럼 검은 화면 + 차단 메시지가 간헐적으로 나타났다.
이 콘텐츠는 차단되어 있습니다. 문제를 해결하려면 사이트 소유자에게 문의하세요.
처음엔 "로그인했을 때만 안 보이나?"처럼 보였지만, 실제로는 로그인 여부와 무관하게 보일 때도 있고 안 보일 때도 있는 간헐 이슈였다.
핵심 힌트
결정적인 단서는 위 차단 문구였다.
이 문구는 YouTube 자체 오류라기보다, 브라우저가 현재 문서의 보안 정책(CSP) 때문에 iframe을 막을 때 흔히 보이는 패턴이다.
즉, 원인 후보가 이쪽으로 좁혀졌다.
- 네트워크 불안정 ❌
- 로그인 상태 버그 ❌
- 브라우저 정책 + 응답 헤더(CSP) ✅
CSP가 정확히 뭔가
CSP는 Content Security Policy의 약자다.
한 줄로 요약하면:
브라우저에게 "이 페이지에서 어떤 외부 리소스를 어디까지 허용할지"를 선언하는 보안 규칙
서버는 응답 헤더로 이런 값을 내려준다.
Content-Security-Policy: default-src 'self'; frame-src https://www.youtube.com
브라우저는 이 규칙을 보고, 허용되지 않은 리소스는 로딩을 막는다.
왜 필요한가
가장 큰 목적은 XSS 같은 스크립트 주입 공격을 줄이는 것이다.
- 내가 의도하지 않은 스크립트 실행 차단
- 의도하지 않은 외부 도메인 요청 차단
- iframe, 이미지, 폰트, API 호출 출처를 제한
이번 이슈와 직접 관련된 지시어
| 지시어 | 의미 | 이번 케이스 |
|---|---|---|
default-src | 기본 허용 출처 | 전체 기본 안전망 |
script-src | JS 로드 허용 출처 | 분석/광고 스크립트 허용 |
connect-src | XHR/fetch/SSE 허용 출처 | API/Analytics 호출 |
img-src | 이미지 허용 출처 | CDN/스토리지 이미지 |
frame-src | iframe 허용 출처 | YouTube 임베드 핵심 |
이번 문제는 frame-src에 YouTube가 없어서, 브라우저가 YouTube iframe을 보안상 차단한 케이스였다.
즉, 코드가 맞아도 CSP가 틀리면 화면은 막힌다.
왜 "검은 화면 + 차단 문구"가 뜨나
iframe 내부 콘텐츠는 브라우저가 먼저 보안 정책으로 검사한다.
frame-src에 도메인이 있으면 렌더링 시도- 없으면 로딩 자체 차단
- 사용자 입장에서는 "영상 플레이어가 고장난 것처럼" 보임
그래서 이슈를 디버깅할 때는 JS 에러 콘솔만 볼 게 아니라, 문서 응답 헤더의 CSP를 함께 확인해야 한다.
왜 "간헐적"으로 보였나
여기가 이번 이슈의 포인트였다.
우리 서비스는 Next.js 기반이고, 블로그 상세 진입이 상황에 따라 두 가지로 갈린다.
- 문서 새로 로드(하드 네비게이션)
- SPA 클라이언트 라우팅(소프트 네비게이션)
기존 CSP는 next.config.js에서 특정 경로에만 적용하고 있었고, 경로별 진입 방식/캐시 상태에 따라 체감이 달라졌다. 그래서 같은 글인데도 어떤 때는 보이고, 어떤 때는 막히는 것처럼 보였다.
실제 원인
frontend/next.config.js에서 블로그 경로에 CSP를 넣어둔 것은 맞았는데, frame-src에 YouTube 도메인이 빠져 있었다.
기존 설정(문제):
"frame-src https://*.googlesyndication.com https://googleads.g.doubleclick.net"
즉, 광고 iframe은 허용하지만 YouTube iframe은 허용하지 않는 상태였다.
수정 1: CSP에 YouTube 도메인 허용
frame-src를 아래처럼 확장했다.
"frame-src https://*.googlesyndication.com https://googleads.g.doubleclick.net https://www.youtube.com https://youtube.com https://www.youtube-nocookie.com https://youtube-nocookie.com"
수정 파일:
frontend/next.config.js
수정 2: iOS(WebKit) 임베드 안정화
CSP만으로도 차단 문제는 해결되지만, iPhone 계열에서 재생 안정성을 더 높이기 위해 임베드 컴포넌트도 함께 보강했다.
변경 내용:
youtube.com/embed→youtube-nocookie.com/embed사용playsinline=1,rel=0,modestbranding=1파라미터 추가- iframe 레이아웃 단순화 (
absolute제거,w-full h-full block) loading="lazy"제거
수정 파일:
frontend/src/shared/ui/YouTubeEmbed.tsx
핵심 코드:
const embedUrl = buildYouTubeEmbedUrl(videoId, startTime) const iframeUrl = new URL(embedUrl) iframeUrl.hostname = 'www.youtube-nocookie.com' iframeUrl.searchParams.set('rel', '0') iframeUrl.searchParams.set('playsinline', '1') iframeUrl.searchParams.set('modestbranding', '1')
검증
배포 전 로컬 검증은 아래로 통과했다.
cd frontend && npm run build✅cd backend && ./gradlew test✅
추가 확인 포인트:
- iPhone Chrome/Safari에서 블로그 상세 새로고침 진입
- YouTube iframe 정상 렌더링 확인
- 문제가 있던 문서에서 차단 메시지 재발 여부 확인
- 네트워크 탭에서 문서 응답의
Content-Security-Policy헤더에frame-src ... youtube ...포함 확인
이번 이슈에서 배운 점
1) "검은 화면"은 플레이어 버그가 아닐 수 있다
임베드 문제는 플레이어 코드보다 **보안 헤더(CSP)**가 원인인 경우가 많다.
2) SPA + 헤더 정책은 체감이 복잡해진다
사용자는 "간헐적"으로 느끼지만, 실제로는 라우팅 방식/캐시/헤더 적용 범위의 조합일 수 있다.
3) iframe 기능을 만들 때 체크리스트가 필요하다
frame-src허용 도메인 점검- 모바일 WebKit 동작 점검
youtube-nocookie/playsinline같은 실전 파라미터 적용
마무리
이번 장애는 "YouTube가 가끔 고장 난다"가 아니라,
CSP 정책 누락 + iOS WebKit 특성이 겹친 문제였다.
결국 해결은 화려한 코드가 아니라, 헤더 한 줄과 임베드 설정 몇 가지였다.
하지만 이런 "한 줄"을 찾기까지가 늘 제일 어렵다.






댓글
댓글을 작성하려면 이 필요합니다.