애드센스 심사는 블로그가 아니라 메인 도메인이 받습니다

애드센스 승인은 블로그 서브도메인이 아니라 메인 도메인이 받습니다. 그래서 goodtek.xyz를 단순 랜딩이 아닌 블로그·서비스·정책이 연결되는 콘텐츠 허브로 다시 정리했습니다.

Share
애드센스 심사는 블로그가 아니라 메인 도메인이 받습니다

가끔 개발하다 보면 이상한 순간에 일이 순식간에 불어나네요.

이번에도 그랬습니다.
처음에는 그냥 “메인 랜딩에 블로그 최신글 몇 개 붙이면 되겠지” 정도로 생각했습니다. Ghost 블로그는 이미 따로 운영 중이고, Content API도 있으니 최신글 3개, 추천글 3개 정도 가져오면 끝날 일처럼 보였습니다.

그런데 막상 TASK를 정리하다가 조금 불편한 사실 하나가 눈에 들어왔습니다.

애드센스 승인을 받으려는 곳은 blog.goodtek.xyz가 아니라 goodtek.xyz라는 점이었습니다.

블로그에는 글이 있습니다.
긴 글도 있고, 검색 유입을 기대할 만한 콘텐츠도 있습니다.
그런데 실제로 심사를 받을 메인 도메인은 랜딩 페이지입니다.

그러면 질문이 바뀝니다.

“블로그 최신글을 어떻게 붙일까?”가 아니라,

goodtek.xyz는 정말 하나의 사이트처럼 보이는가?

여기서 살짝 현타가 왔습니다.
저는 블로그를 열심히 쓰고 있었는데, 정작 심사를 받을 문은 아직 얇았습니다.

블로그는 있는데, 심사는 랜딩이 받는 이상한 구조

goodtek의 콘텐츠 구조는 처음부터 완전히 한곳에 모여 있지 않았습니다.

Ghost 블로그는 blog.goodtek.xyz에 있고, 메인 웹앱은 goodtek.xyz에 있습니다. 앞으로 커뮤니티, 노트, 자동화 도구, SaaS 페이지까지 붙을 수 있습니다. 이 구조 자체는 나쁘지 않습니다. 오히려 각 서비스의 역할을 나누기에는 좋습니다.

문제는 역할이 나뉘어 있다는 사실을 방문자와 심사 시스템이 이해할 수 있어야 한다는 것이었습니다.

구분goodtek.xyzblog.goodtek.xyz
역할퍼블리셔 허브, 메인 사이트전문 글 저장소
콘텐츠About, Privacy, Explore, 글 teaserGhost 본문 전체
목적신뢰, 탐색, 연결롱테일 SEO, 깊은 글
주의점본문 전체 재게시 금지원문 유지

처음에는 메인에도 블로그 글 전문을 보여줄까 잠깐 고민했습니다.
하지만 그건 방향이 이상했습니다.

메인 도메인은 블로그를 복제하는 곳이 아니라, 블로그로 보내는 허브여야 했습니다.

그래서 기준을 이렇게 잡았습니다.

전문은 블로그에 둔다.
메인에는 최신글과 추천글 teaser만 보여준다.
사용자가 관심을 가지면 blog.goodtek.xyz로 이동한다.

이렇게 해야 중복 콘텐츠 문제도 피하고, 메인 도메인도 “내용 없는 랜딩”이 아니라 “콘텐츠를 탐색할 수 있는 사이트”가 됩니다.

메인 도메인을 풍성하게 만든 이유

랜딩 페이지가 아니라 서비스 허브

이번 작업을 하면서 제일 많이 바뀐 단어가 있습니다.

처음에는 계속 “랜딩 페이지”라고 불렀습니다.
그런데 만들다 보니 이 표현이 조금 좁게 느껴졌습니다.

랜딩 페이지라고 하면 보통 한 가지 액션을 유도하는 페이지에 가깝습니다.
구독하기, 문의하기, 다운로드하기, 구매하기 같은 것들입니다.

그런데 goodtek.xyz가 앞으로 해야 할 일은 그것보다 넓습니다.

블로그로 보내고, 커뮤니티로 보내고, 작업 로그로 보내고, 프로젝트 소개로 보내고, 나중에는 실제 앱이나 SaaS로도 연결해야 합니다. 그러면 이건 단순한 랜딩이 아니라 서비스 허브에 가깝습니다.

goodtek.xyz
├─ About / Explore
├─ Blog teaser
│  └─ blog.goodtek.xyz 원문으로 이동
├─ Privacy / Contact
├─ Community
├─ Notes
└─ 앞으로 붙을 App / SaaS

이 구조를 생각하고 나니 HomeBlogSection의 위치도 자연스럽게 정해졌습니다.

Hero 바로 아래.
Explore 위.

방문자가 goodtek이 무엇을 하는지 본 다음, 바로 실제 콘텐츠를 확인할 수 있어야 했습니다. 그리고 더 깊이 보고 싶으면 블로그로 이동하면 됩니다.

Hero → Blog → Explore → CTA → Footer

이 순서는 단순히 예뻐 보이려고 정한 게 아닙니다.
방문자의 흐름을 생각한 결과입니다.

  1. 여기가 무엇을 하는 곳인지 본다.
  2. 실제로 쌓인 콘텐츠가 있는지 확인한다.
  3. 관심 있는 주제나 서비스로 더 들어간다.
  4. 필요하면 구독하거나 문의한다.
  5. 정책과 연락처를 확인할 수 있다.

이렇게 보면 블로그 섹션은 장식이 아닙니다.
goodtek이 실제로 계속 쌓고 있는 기록을 메인 도메인에서 증명하는 장치입니다.

goodtek.xyz 서비스 허브 구조

TASK-009에서 실제로 한 일

이번 TASK-009의 목표는 한 줄로 요약하면 이렇습니다.

Ghost 블로그의 Content API를 이용해서 메인 홈에 최신글과 추천글을 연결하고, 메인 도메인이 AdSense 신청 사이트로 보일 수 있는 기본 구조를 갖추기.

겉으로 보면 블로그 카드 6개가 추가된 작업입니다.
하지만 실제로는 콘텐츠 전략, SEO, AdSense 승인 기준, 도메인 역할 분리까지 같이 묶인 작업이었습니다.

구현 범위는 이렇게 정리했습니다.

영역작업이유
Ghost 연동Latest 3, Featured 3 가져오기메인 홈에서 실제 콘텐츠 노출
HomeBlogSectionHero 아래 배치방문 초반에 콘텐츠 신뢰도 전달
Privacyko/en 개인정보처리방침 추가AdSense 신청에 필요한 기본 신뢰 요소
Header/FooterBlog, Privacy 링크 추가사이트 탐색성과 정책 접근성 확보
환경변수Ghost URL/API Key 추가운영 환경에서 안전하게 연동

여기서 일부러 하지 않은 것도 있습니다.

  • 메인 도메인에 /blog/[slug]를 만들어 전문을 미러링하지 않기
  • Ghost를 docker compose 로컬 구성에 추가하지 않기
  • AdSense 스크립트나 쿠키 배너를 지금 넣지 않기
  • 조회수 기반 인기글을 무리해서 만들지 않기

이런 것들은 “못 해서” 뺀 게 아니라, 지금 단계에서 오히려 구조를 흐릴 수 있어서 뺐습니다.

특히 전문 미러링은 조심해야 했습니다.
메인 도메인이 풍성해지는 것과, 블로그 본문을 그대로 복제하는 것은 다릅니다.

코드보다 기준이 먼저였던 작업

구현 자체는 아주 복잡한 편은 아니었습니다.

Ghost Content API에서 글을 가져오고, 최신글과 추천글을 나눠서 보여주고, 실패하면 fallback UI를 보여주는 구조입니다. 중요한 건 API 호출보다 어디까지 가져오고 어디서 멈출 것인가였습니다.

핵심 아이디어는 이 정도였습니다.

홈 화면
├─ 최신 글 3개
│  └─ Ghost Content API에서 가져오기
├─ 추천 글 3개
│  ├─ featured:true 우선
│  └─ 없으면 GHOST_FEATURED_SLUGS 사용
└─ 클릭 시 blog.goodtek.xyz 원문으로 이동

이 구조 덕분에 메인 도메인은 글 목록을 보여주지만, 글의 주인은 계속 블로그입니다.

조금 더 코드에 가까운 흐름으로 보면 이런 느낌입니다.

const latestPosts = await fetchLatestPosts(3)

const featuredPosts =
  await fetchFeaturedPosts(3)
// featured 글이 없으면
// GHOST_FEATURED_SLUGS 기준으로 fallback

return {
  latestPosts,
  featuredPosts,
}

짧은 코드처럼 보이지만, 여기에는 기준이 들어 있습니다.

전문은 가져오지 않는다.
excerpt와 썸네일 중심으로 보여준다.
링크는 블로그 원문으로 보낸다.
API Key는 서버 환경변수로만 둔다.

이런 기준이 없으면 구현은 빨리 끝나도 나중에 다시 흔들립니다.

로컬에서 만난 의외의 함정

작업 중에 작은 삽질도 있었습니다.

Ghost API는 직접 호출하면 정상적으로 응답했습니다. HTTP 200도 나왔고, 글도 잘 보였습니다. 그런데 Next.js 앱에서는 계속 fallback UI가 보였습니다.

처음에는 API 키 문제인가 싶었습니다.
그런데 아니었습니다.

문제는 Next.js가 .env를 읽는 위치였습니다.

루트 .env에는 Ghost 관련 값이 잘 들어가 있었지만, Next 앱은 apps/web에서 실행되고 있었습니다. 그래서 기본적으로 루트 .env를 읽지 못했습니다.

여기서 다시 한 번 느꼈습니다.

“API가 안 된다”는 말은 너무 넓습니다.
실제로는 API가 안 되는 게 아니라, 앱이 API 설정을 못 읽는 상태였습니다.

이 차이를 구분하지 않으면 엉뚱한 곳을 계속 보게 됩니다.

결국 next.config.ts에서 monorepo 루트의 .env를 로드하도록 수정했고, 이후 Ghost 글이 메인 홈에 정상적으로 표시됐습니다.

작은 문제였지만 이런 문제가 개발 시간을 잡아먹습니다. 그리고 이런 게 나중에 운영 문서가 됩니다.

애드센스 때문만은 아닌 이유

솔직히 말하면 이번 작업의 출발점에는 AdSense가 있었습니다.

goodtek은 앞으로 Google AdSense 광고 수입도 하나의 수입원으로 실험해 보려고 합니다. 그러려면 심사를 받아야 하고, 심사를 받는 대상은 메인 도메인입니다.

하지만 작업을 끝내고 나니 이게 단순히 “승인받기 위한 페이지 보강”으로만 느껴지지는 않았습니다.

AdSense를 떠나서도, goodtek에는 필요한 구조였습니다.

블로그가 따로 있고, 커뮤니티가 따로 있고, 노트가 따로 있고, 프로젝트가 따로 있다면 결국 사용자는 길을 잃습니다.
각각의 서비스가 좋아도, 입구가 흩어져 있으면 전체 브랜드의 힘이 약해집니다.

그래서 goodtek.xyz는 앞으로 이런 역할을 해야 합니다.

역할설명
탐색의 시작점처음 온 사람이 goodtek이 무엇을 하는지 이해
콘텐츠 허브블로그, 노트, 커뮤니티로 자연스럽게 이동
신뢰의 기준점About, Contact, Privacy 같은 기본 정보 제공
수익화의 기반AdSense, SaaS, 제품 페이지로 확장 가능한 구조

이제 goodtek.xyz는 단순한 소개 페이지가 아니라, 흩어진 작업을 하나의 맥락으로 묶는 중심 페이지가 되어야 합니다.

저는 이걸 랜딩보다 “서비스 허브”라고 부르는 게 더 맞다고 느꼈습니다.

랜딩보다 더 맞는 말, 서비스 허브

TASK 종료 전 체크한 것들

이번 TASK를 닫기 전에 확인한 기준은 꽤 현실적이었습니다.

  • 최신글 3개가 정상적으로 보이는가
  • 추천글 3개가 정상적으로 보이는가
  • Featured 글이 없을 때 slug fallback이 동작하는가
  • Ghost API Key가 클라이언트에 노출되지 않는가
  • Header에서 Blog로 이동할 수 있는가
  • Footer에서 Privacy와 Blog에 접근할 수 있는가
  • /ko/privacy, /en/privacy가 존재하는가
  • 빌드와 린트가 통과하는가
  • 메인 도메인에 Ghost 본문 전문을 복제하지 않았는가

마지막 항목이 특히 중요했습니다.

기능은 더 넣을 수 있습니다.
하지만 지금 단계에서는 더 넣는 것보다 역할을 분리하는 것이 더 중요했습니다.

메인 도메인 심사 전 체크리스트

ship, merge, 그리고 다음 고민

마지막으로 vibeops task ship을 실행했고, TASK-009는 PR로 올라갔습니다.

PR은 develop으로 squash merge 됐습니다.
상태도 Shipped로 바뀌었습니다.

겉으로 보면 하나의 TASK가 끝난 것입니다.
하지만 제 기준에서는 오히려 다음 질문이 더 선명해졌습니다.

goodtek.xyz는 앞으로 무엇을 더 품어야 할까?

블로그 최신글을 붙인 건 시작입니다.
그다음에는 커뮤니티 글, 공개 작업 로그, 프로젝트별 빌드 노트, SaaS 제품 페이지가 붙을 수 있습니다. 그리고 어느 순간 goodtek.xyz는 “소개 페이지”가 아니라, goodtek이 빌드하는 모든 것의 입구가 될 겁니다.

처음엔 AdSense 승인을 위해 메인 도메인을 풍성하게 만들려고 했습니다.
그런데 만들다 보니 결국 남은 건 수익화보다 더 기본적인 문제였습니다.

좋은 서비스는 기능만 있어서는 부족하고,
사람들이 길을 잃지 않는 입구가 필요하다.

이번 TASK-009는 그 입구를 조금 넓힌 작업이었습니다.

아직 문패를 단 정도일지도 모릅니다.
하지만 이제 goodtek.xyz는 빈 랜딩이 아니라, 블로그와 서비스들이 연결되는 첫 번째 허브가 되기 시작했습니다.

빌드 여정을 함께하고 싶으신 분들은 언제든지 환영입니다.

Read more

바이브코딩이 꼬이는 순간, 문제는 코드가 아니라 사이클이었다

바이브코딩이 꼬이는 순간, 문제는 코드가 아니라 사이클이었다

처음에는 단순한 문제처럼 보였습니다. goodtek website를 만들면서 VibeOps를 붙여 쓰고 있었고, 흐름도 그럴듯했습니다. TASK를 만들고, 브랜치를 따고, Cursor로 구현하고, 커밋하고, 푸시하고, MR을 만들고, 머지하면 끝. 그런데 이상하게도 끝난 것 같은데 끝나지 않았습니다. task done을 실행했는데 git status는 여전히 dirty였습니다. MR은 올라갔는데 로컬 TASK 문서는 또 바뀌어 있었습니다. 머지한 뒤에 develop을

By ● goodtek