🚨 플러터 웹, 당신의 사이트가 구글에서 '증발'하는 이유 (아무도 알려주지 않은 함정!)
2026. 8. 24.
🚨 플러터 웹, 당신의 사이트가 구글에서 '증발'하는 이유 (아무도 알려주지 않은 함정!)
최근에 플러터 웹으로 공들여 만든 개인 포트폴리오를 배포했습니다. 링크드인으로 몇몇 분께 공유했는데, 영 찜찜한 두 가지 현상을 발견했어요.
- WhatsApp 같은 메신저에서 링크 미리보기가 전혀 뜨지 않고, 날것 그대로의 URL만 보이더군요.
- 제 이름으로 구글 검색을 해보니, 분명 사이트 안에 제 이름이 수십 번은 나올 텐데, 결과는 처참했습니다. 단 한 줄도 제 사이트 내용이 노출되지 않는 겁니다.
원인은 두 현상 모두 동일했고, 솔직히 알고 나면 '아차!' 싶을 정도로 간단했습니다. 바로 **플러터 웹 앱은 기본 렌더러인 CanvasKit으로 빌드될 경우, 모든 UI를 단 하나의 <canvas> 태그 안에 '그려 넣는다'**는 점이죠. DOM(Document Object Model) 안에는 <h1> 태그도, <p> 태그도, 심지어 어떤 텍스트 콘텐츠도 존재하지 않습니다. 사용자 브라우저에서는 이 앱이 매끄럽게 돌아가는 것처럼 보입니다. 여러분이 다트(Dart) 코드로 구현한 반응형 UI와 화려한 애니메이션이 완벽하게 작동하죠. 하지만 구글 크롤러, WhatsApp 링크 미리보기를 생성하는 봇, 시맨틱스 지원이 부족한 스크린 리더처럼 '오직 HTML만 읽는' 존재들에게는 어떨까요? 그들에게 이 페이지는 문자 그대로 <canvas> 태그 하나만 덜렁 있는 백지나 다름없습니다.
제가 실무에서 이 부분을 테스트해 봤을 때, 처음에는 '개발자 도구에서 분명 텍스트가 보이는데 왜 크롤러는 못 읽지?' 하는 의문이 들었어요. 나중에 DOM 구조를 직접 살펴보니, 그 텍스트들이 모두 캔버스 위에 그려진 이미지 조각처럼 작동하고 있었다는 걸 깨달았죠.
이 사실은 10초 만에 직접 확인해 볼 수 있습니다. 아래 명령어는 여러분의 웹사이트에서 특정 텍스트가 HTML 소스 코드에 실제로 존재하는지 확인하는 간단한 방법입니다.
curl -s https://your-site.netlify.app/ | grep -c "your name"
# 0
결과는 '0'입니다. Text() 위젯 안에 아무리 많은 내용을 작성했더라도, 서버가 실제로 제공하는 HTML에는 그 어떤 글자도 흔적을 남기지 않는다는 뜻이죠.
그렇다면 이 '투명 인간' 문제를 어떻게 해결할까요?
안타깝지만, 플러터 앱이 캔버스 대신 DOM에 직접 렌더링하도록 하는 '마법의 스위치' 같은 건 없습니다. (예전에 HTML 렌더러가 있긴 했지만, 시각적 일관성 문제로 이미 deprecated 됐죠.) 현실적인 방법은 앱이 캔버스 위에서 실행된다는 사실을 받아들이고, 다트(Dart) 코드가 아닌 index.html 파일 자체를 크롤러 친화적으로 보완하는 것입니다.
이것은 앱이 로드되기 전, 즉 브라우저가 HTML 파일을 처음 읽을 때 검색 엔진이 필요한 정보를 제공하는 작업입니다.
1. 시각적으로는 숨겨져 있지만, 실제 텍스트 콘텐츠 블록을 추가하세요. (단, display: none은 절대 금물!)
이 부분이 정말 중요합니다. display: none이나 visibility: hidden 같은 속성은 과거 키워드 스터핑(Keyword Stuffing)에 악용된 사례가 많아서, 검색 엔진이 해당 콘텐츠를 아예 무시하거나 가치를 낮게 평가하는 경향이 있습니다. 올바른 기법은 수년간 웹 접근성을 위해 사용되어 온 sr-only 클래스(Screen Reader Only) 방식입니다.
#seo-fallback {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
}
이렇게 하면 콘텐츠는 DOM에 그대로 존재하면서도 화면상으로는 전혀 공간을 차지하지 않습니다. 또한, 페이지에 실제로 있는 내용과 동일한 콘텐츠를 평문(plain text) 형태로 제공하는 것이기 때문에 클로킹(Cloaking)으로 간주되지 않습니다. 제 포트폴리오의 경우, 플러터의 Semantics 트리가 스크린 리더에게 이미 노출하고 있던 내용(이름, 역할, 요약, 경험, 기술 등)과 완전히 동일한 텍스트를 사용했습니다. 접근성과 검색 엔진 색인(Index) 성능을 동시에 향상시키는 진정한 '일거양득' 전략이죠.
2. 완전한 Open Graph 및 Twitter Card 메타태그를 추가하세요.
특히 1200×630 픽셀의 고정 크기 og:image는 필수입니다. 이 태그가 없으면 여러분이 보낸 링크는 미리보기 없이 밋밋하게 보일 뿐입니다. 이런 작은 차이가 클릭으로 이어질지, 아니면 스크롤 되어 지나칠지를 결정하곤 하죠. 링크 미리보기는 사용자의 관심을 끄는 가장 기본적인 요소니까요.
3. schema.org/Person을 활용한 JSON-LD를 추가하세요.
이것은 사람이 읽기 위한 텍스트가 아닙니다. '이 웹사이트는 어떤 사람에 대한 것이고, 그 사람의 역할은 X이며, 사이트 주소는 Y다'와 같이 구글에 구조화된 정보를 전달하는 블록이죠. 검색 엔진이 페이지의 내용과 맥락을 더 정확하게 이해하도록 돕습니다.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Your Name",
"jobTitle": "Mobile Software Engineer",
"url": "https://yoursite.com/",
"sameAs": ["https://linkedin.com/in/..."]
}
</script>
4. robots.txt와 sitemap.xml은 기본 중의 기본!
너무나 당연하게 들리지만, flutter create로 새로 생성한 프로젝트에는 이 둘이 기본적으로 포함되어 있지 않습니다. 이 파일들이 없으면 구글에게 '여기 새로운 페이지가 있으니 와서 크롤링해 줘!' 하고 처음으로 알려줄 방법이 없는 셈이죠. 사이트맵은 페이지 구조를 명확히 전달하고, robots.txt는 크롤링 정책을 알려주는 중요한 역할을 합니다.
이 모든 노력의 실질적인 결론은?
이러한 변경 사항들을 적용한 후, 이전에는 '0'을 반환하던 curl 명령어가 이제는 전체 콘텐츠를 제대로 보여줍니다. 자바스크립트가 실행되기 전, 순수한 HTML 안에 모든 내용이 담겨있기 때문이죠.
curl -s https://your-site.netlify.app/ | grep -c "your name"
# 1
그리고 이제 어떤 앱에든 링크를 붙여넣으면, 이미지와 함께 제목, 설명이 예쁘게 뜨는 것을 확인할 수 있습니다. 이제 더 이상 '투명 인간' 사이트가 아닌, 검색 엔진과 메신저 모두에게 제대로 인식되는 페이지가 된 겁니다.
이 네 가지 해결책 중 어느 하나도 다트(Dart) 코드를 한 줄도 건드릴 필요가 없습니다. 모든 작업은 web/index.html 파일과 그 옆의 정적 파일들에서 이루어지죠. 생각해보면 당연한 일입니다. 애초에 문제는 앱 자체가 아니라, 앱이 로드되기도 전에 존재해야 할 것들이 (혹은 존재하지 말아야 할 것들이) 제대로 처리되지 않았다는 것이니까요.
혹시 플러터 웹으로 포트폴리오나 제품 페이지를 운영하고 계신가요? 지금 당장 저 curl 명령어를 실행해보세요. 아마도 '0'이라는 결과가 나올 확률이 아주 높을 겁니다.
원문: https://dev.to/cristovoxdgm/flutter-web-is-invisible-to-google-and-nobody-warns-you-ncm 수집일: 2026-08-24 00:31:29