구글 블로그 테마 만들기 3탄 헤더 푸터 페이징 구현 방법
구글 블로그를 커스텀하다 보면 어느 순간 정체기가 찾아오곤 합니다. 단순히 글만 보여주는 단계를 넘어, 독자가 내 블로그에 들어왔을 때 편리하게 다른 글을 검색하고 탐색할 수 있는 구조를 만들어야 체류 시간이 늘어나고 애드센스 수익도 극대화될 수 있죠. 저번 2편에서 글 리스트와 본문 출력을 제어하는 방식을 다루었다면, 이번 3편에서는 블로그의 뼈대를 완성하는 헤더와 푸터 영역 설정, 모바일 대응을 위한 구조 변경, 그리고 자바스크립트를 활용한 스마트한 함께 보면 좋은 글 목록 구현까지 단계별로 깊이 있게 살펴보겠습니다. 특히 이번에는 각 기능이 전체 XML 코드 중 어느 위치에 들어가야 하는지 명확한 가이드를 함께 공유합니다.
| 요약 주제 | 구글 블로그(Blogger) 커스텀 테마 레이아웃 및 페이징 기능 완성 |
|---|---|
| 핵심 요점 | 헤더/푸터 섹션 분할, 모바일 반응형 CSS 수정, 자바스크립트 기반 연관 글 출력, 페이징 버튼 구현 및 코드 위치 안내 |
| 추천 대상 | 애드센스 최적화를 위해 가볍고 빠른 구글 블로그 기본 테마를 직접 제작하고 코드를 완벽히 제어하려는 운영자 |
* 위 표는 본문의 내용을 요약한 구글 블로그 테마 만들기 3탄 핵심 가이드입니다.
목차
1. 구글 블로그 테마 레이아웃 구조를 삼등분으로 나누어야 하는 이유와 헤더 코드의 위치
2. 검색창 배치와 푸터 영역의 위젯 공간 마련하기
3. 모바일 환경을 고려한 반응형 CSS 스타일 수정과 링크 최적화 코드 위치
4. 다중 아이템 페이지에서만 나타나는 스마트한 페이징 처리 기법과 삽입 위치
5. 자바스크립트와 Feeds API를 활용한 함께 보면 좋은 글 구현 및 상세 위치
6. 구글 블로그 테마 만들기 3탄 핵심 요약
7. 자주 묻는 질문
8. 3편 기능이 모두 반영된 구글 블로그 완성 소스 코드
구글 블로그 테마 레이아웃 구조를 삼등분으로 나누어야 하는 이유와 헤더 코드의 위치
웹사이트나 블로그를 설계할 때 가장 기본이 되는 작업은 바로 영역의 분리입니다. 기존의 단일 섹션 구조에서 벗어나 상단(Header), 중간(Main), 하단(Footer)으로 역할을 명확히 나누어 주는 것이 장기적인 블로그 운영과 관리에 절대적으로 유리합니다. 이렇게 영역을 분할해 두면 향후 특정 영역의 디자인을 수정하거나 새로운 기능을 추가할 때 다른 코드에 영향을 주지 않고 독립적으로 작업할 수 있기 때문입니다.
특히 헤더 영역은 블로그의 첫인상을 결정짓는 간판과도 같습니다. 모든 페이지에서 동일하게 유지되며 독자들에게 일관된 브랜딩을 전달해야 하죠. 이번 설계에서 헤더 영역 코드는 body 태그가 시작되는 바로 직후인 최상단에 배치됩니다. 홈 화면일 때는 블로그 타이틀을 가장 중요한 h1 태그로 표현하고, 상세 글 페이지나 다른 영역으로 이동했을 때는 일반 div 태그로 유연하게 변환되도록 조건문을 구성했습니다. 이렇게 하면 검색엔진 최적화 측면에서 각 페이지의 실제 글 제목이 h1 역할을 온전히 수행할 수 있어 매우 긍정적인 영향을 미치게 됩니다.
Blogger 테마에서 b:cond 조건문을 활용해 메인 화면과 상세 화면의 타이틀 태그 구조를 다르게 가져가는 것은 검색엔진(SEO)이 웹페이지의 주제를 정확히 파악하도록 돕는 고급 테마 최적화 기술 중 하나입니다.
| 구조 영역 | 소스 코드 배치 위치 | 주요 역할 및 SEO 배치 전략 |
|---|---|---|
| 헤더(Header) | <body> 태그 시작 직후 최상단 | 블로그 브랜딩 타이틀(조건별 h1/div 전환), 방문자 내부 검색 기능 제공 |
| 메인(Main) | <header> 종료 후 <main id='main-body'>로 감싸서 배치 | 콘텐츠 본문 출력 공간, 반응형 레이아웃(#content) 적용 영역 |
| 푸터(Footer) | <main> 영역이 모두 끝난 후 <body> 종료 직전 | 카피라이트, 사이트 링크, 위젯 추가 공간(b:section 활성화) |
검색창 배치와 푸터 영역의 위젯 공간 마련하기

아무리 좋은 글이 많아도 방문자가 원하는 정보를 찾지 못해 이탈한다면 큰 손해입니다. 그렇기 때문에 헤더 영역 내부에 직관적인 검색창을 내장하는 것은 필수적입니다. 구글 블로그 고유의 검색 액션 경로를 활용하여 폼 태그를 구성하면, 복잡한 백엔드 작업 없이도 독자들이 내 블로그 내부의 글을 빠르게 검색할 수 있는 훌륭한 기능이 완성됩니다. 이를 통해 독자는 필요한 정보를 연속적으로 탐색할 수 있고, 결과적으로 블로그의 전체 페이지뷰가 상승하는 효과를 기대할 수 있습니다.
반면 푸터 영역은 블로그 운영자의 성향이나 목적에 따라 들어갈 내용이 가장 다양하게 변하는 공간입니다. 누군가는 저작권 표시만 심플하게 남기기를 원하고, 다른 누군가는 주요 카테고리 링크나 태그 클라우드를 배치하고 싶어 하죠. 이 푸터 코드는 전체 소스 코드의 가장 하단, 즉 body 태그가 닫히기 직전에 들어가게 됩니다. 테마 소스 코드 내부에 특정 내용을 고정해 버리기보다는, 관리자 화면의 '레이아웃 편집기'에서 언제든 자유롭게 마우스 드래그로 위젯을 추가하고 뺄 수 있도록 공백의 섹션을 마련해 주는 것이 현명합니다. HTML 위젯을 활용해 자유롭게 레이아웃을 구성해 보세요.
헤더 및 푸터 핵심 구조 코드 레이아웃 위치
블로그의 상단과 하단을 정의하는 표준 HTML 뼈대 구조의 결합 방식입니다.
- <header>: 내부에 조건문 타이틀과 <form action='/search'> 기반의 내부 검색창을 포함하여 body 시작 지점에 삽입
- <footer>: b:section 태그를 배치하여 관리자 화면에서 위젯 제어가 가능하도록 가변 구조로 설계하여 body 종료 지점에 삽입
모바일 환경을 고려한 반응형 CSS 스타일 수정과 링크 최적화 코드 위치
최근 블로그 유입 데이터의 70% 이상은 스마트폰이나 태블릿 같은 모바일 기기에서 발생합니다. 아무리 코드가 훌륭해도 화면이 깨지거나 가독성이 떨어진다면 독자는 즉시 이탈하게 될 텐데요. 디자인과 스타일을 담당하는 CSS 코드들은 반드시 head 태그 내부에 위치한 <b:skin><![CDATA[ ... ]]></b:skin> 영역 안에 넣어주어야 합니다. 이 영역 안에 코드를 추가해야 구글 블로그 시스템이 스타일 시트로 정상 인식하고 화면을 부드럽게 렌더링합니다.
기존 테마 스타일에서 한 걸음 더 나아가 전체 콘텐트 영역을 감싸는 컨테이너 구조에 max-width 속성과 width: 100%, 그리고 box-sizing: border-box를 함께 부여하여 어떤 화면 크기에서도 유연하게 늘어나고 줄어드는 완벽한 반응형 레이아웃을 구축해야 합니다. 또한 사용자가 링크를 클릭할 때 시각적인 피드백을 명확히 주는 것도 사용자 경험(UX) 관점에서 대단히 중요합니다. 일반적인 텍스트와 링크가 걸린 텍스트가 명확히 구분되도록 세련된 블루 톤의 색상을 지정하고, 마우스를 올렸을 때 자연스럽게 색상이 변하도록 트랜지션 효과를 추가해 주면 전체적인 블로그의 완성도가 한층 더 높아집니다.
패딩이나 테두리 두께가 추가될 때 레이아웃 전체 폭이 깨지는 현상을 방지하려면 b:skin 내부 CSS 설정 시 반드시 box-sizing: border-box 속성을 함께 선언해 주어야 모바일 화면에서 좌우 스크롤이 생기는 오류를 막을 수 있습니다.
다중 아이템 페이지에서만 나타나는 스마트한 페이징 처리 기법과 삽입 위치
블로그 홈 화면이나 카테고리 페이지에 접속했을 때 모든 글이 한 화면에 끝없이 내려온다면 로딩 속도도 느려지고 방문자도 피로감을 느끼게 됩니다. 구글 블로그는 이전 글과 다음 글로 이동할 수 있는 고유의 페이지 URL 데이터를 제공하는데, 이를 활용해 하단 페이징 내비게이션을 만들어 주어야 합니다. 이 페이징 코드가 들어가는 정확한 위치는 글 출력을 담당하는 반복문인 </b:loop> 태그가 끝난 직후이자, 해당 위젯 인클루더인 </b:includable> 태그가 닫히기 전입니다.
여기서 중요한 설계 포인트는 이 페이징 버튼이 '글 목록 화면'에서만 보이고, 개별 게시글을 읽는 '본문 상세 화면'에서는 나타나지 않도록 제어하는 것입니다. Blogger 테마 문법 중 하나인 data:view.isMultipleItems 조건문을 사용하면 이 문제를 아주 우아하게 해결할 수 있습니다. 메인 루프가 끝나고 난 뒤 해당 조건문 영역 내부에서만 페이징 코드가 작동하도록 배치하면, 시스템이 자동으로 현재 페이지의 성격을 파악하여 필요한 순간에만 '이전 글'과 '다음 글' 버튼을 화면에 뿌려주게 됩니다. 불필요한 DOM 객체 생성을 줄여 웹서핑 속도를 한결 가볍게 유지해 주는 비결이기도 합니다.
페이징 처리 조건문 흐름과 위치 예시
화면의 성격에 따라 페이징 내비게이션 노출 여부를 판단하는 내부 로직과 소스 트리상의 위치입니다.
- 위치: 메인 블로그 포스트 반복 출력문인 </b:loop> 코드 바로 아래 배치
- 메인 / 라벨 / 검색 결과 페이지 (= MultipleItems) -> 페이징 내비게이션 코드 활성화 및 노출
- 게시글 상세 페이지 (= 싱글 포스트) -> 페이징 내비게이션 코드 무시 및 숨김 처리
자바스크립트와 Feeds API를 활용한 함께 보면 좋은 글 구현 및 상세 위치
방문자가 하나의 글을 모두 읽은 뒤 블로그를 나가지 않고 또 다른 글을 소비하게 만드는 가장 좋은 방법은 본문 하단에 연관된 추천 글을 제안하는 것입니다. 이 기능이 들어가는 정확한 위치는 본문 상세 보기를 출력하는 <div class='post-body'><data:post.body/></div></article> 태그의 바로 아랫부분입니다. 즉, 독자가 글을 모두 읽고 난 뒤 시선이 머무는 기사 하단 영역에 자연스럽게 노출되도록 배치해야 합니다.
구글 블로그 기본 기능만으로는 이를 완벽하게 구현하기 까다롭기 때문에, 우리는 구글 블로그가 제공하는 내부 Feeds API와 자바스크립트의 fetch 함수를 결합하여 역동적인 '함께 보면 좋은 글' 박스를 만들 수 있습니다. 현재 읽고 있는 글에 설정된 첫 번째 라벨(카테고리)의 이름을 추출하여 해당 라벨을 공유하는 최신 글 5개를 실시간으로 불러오는 방식입니다. 코드 내부에서 현재 포스트의 고유 ID와 피드로 받아온 아이템들의 ID를 비교하여 일치하는 항목을 걸러내 주면 독자에게 진짜 유용한 다른 글들만 깔끔하게 리스트 형태로 제공할 수 있습니다.
fetch API를 사용해 블로그 자체 피드 데이터를 비동기식으로 호출하는 방식은 무거운 외부 플러그인을 설치하는 것보다 페이지 초기 로딩 속도(Core Web Vitals)를 저해하지 않아 구글 상위 노출 레이팅에 훨씬 유리합니다.
구글 블로그 테마 만들기 3탄 핵심 요약
이번 장에서 다룬 가볍고 빠른 구글 블로그 테마 레이아웃 완성의 핵심 정리입니다.
- 영역의 명확한 분할: 헤더는 body 최상단, 푸터는 body 최하단으로 독립 분할하여 유지보수성을 극대화합니다.
- 스타일 및 페이징 위치: CSS는 head 태그 안 b:skin 내부에 넣고, 페이징 버튼은 메인 포스트 루프문이 끝나는 b:loop 직후에 배치합니다.
- 비동기 추천 데이터 연동: 상세 페이지 본문 태그인 article 종료 직후에 자바스크립트 연관 글 코드를 삽입하여 체류 시간을 확보합니다.
자주 묻는 질문
구글 블로그를 나만의 스타일로 하나씩 빌드업해 나가는 과정은 처음에는 다소 낯설고 코드의 위치를 찾는 것이 헷갈릴 수 있지만, 불필요한 코드를 모두 걷어낸 초경량 수익형 테마를 완성했을 때의 만족감은 이루 말할 수 없습니다. 아래에 각 기능이 완벽하게 결합된 완성 소스 코드를 공유해 드립니다. 소스 내 주석과 구조를 천천히 음미해 보시길 권장합니다. 다음 4편에서는 구글 블로그 테마의 깊이를 한 층 더해줄 고유 아이디 값의 활용 방법 및 예약어 속성들을 상세히 분석하여 가이드해 드릴 테니 많은 기대 부탁드립니다. 차근차근 위치에 맞춰 코드를 적용해 보세요.
3편 기능이 모두 반영된 구글 블로그 완성 소스 코드
본 소스는 랭크블로그에서 직접 제작한 소스로, 블로그 독자분들의 멋진 블로그 운영을 위해 무료로 제공해 드리고 있습니다.
✔ [주의] 개인적인 학습 및 본인 블로그 커스텀 목적으로만 사용해 주시기 바라며, 무단 재배포, 상업적 판매 등 악의적인 목적으로 이용하거나 악용하는 행위는 엄격히 금지합니다.
현재 공유해 드린 기본 뼈대 코드 외에, 추가적인 최적화 기능이 결합되어 실제로 활발히 운영 중인 샘플 블로그의 모습이 궁금하시다면 아래 링크를 확인해 보세요. 노후준비 A to Z 블로그에서 실제 적용 예시 확인하기 →
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html b:css='false' b:defaultwidgetversion='2' b:layoutsversion='3' b:responsive='true' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2004/bml' xmlns:data='http://www.google.com/2004/bml/data' xmlns:expr='http://www.google.com/2004/bml/expr' expr:dir='data:blog.languageDirection' expr:lang='data:blog.locale'>
<head>
<meta content='width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1' name='viewport'/>
<title><data:view.title.escaped/></title>
<b:include data='blog' name='all-head-content'/>
<b:skin><![CDATA[
/*
[관리자 레이아웃 편집기용] - 오직 관리자 레이아웃 화면에만 사용되는 CSS
*/
body#layout #content {
display: block !important;
max-width: 1200px !important;
width: 100% !important;
margin: auto;
}
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.7; color: #333; }
/* 3편에서 추가된 기본 링크 */
a {
color: #0066cc;
text-decoration: none;
transition: all 0.25s ease;
}
a:hover {
color: #004499;
}
/* 3편에서 추가된 헤더 영역 - 로고와 검색창 세로 배치 */
header { display: flex; flex-direction: column; align-items: center; padding: 30px 20px; border-bottom: 1px solid #eee; gap: 20px; }
.blog-title { flex: 1; text-align: center; font-size: 24px; font-weight: bold; line-height: 40px; margin: 0; }
/* 검색창 영역 */
.search-box { width: 100%; max-width: 400px; }
.search-box form { display: flex; flex-wrap: wrap; width: 100%; gap: 5px; }
.search-box input { flex: 1 1 200px; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
.search-box button { padding: 8px 15px; cursor: pointer; }
/* 3편에서 변경된 css - 모바일 대비 변경 */
#content {
max-width: 1024px;
margin: auto;
padding: 30px;
display: flex;
box-sizing: border-box;
width: 100%;
}
/* 2편에서 추가된 css */
/*
메인 포스트 목록 레이아웃 관련
*/
article { margin-bottom: 40px; border-bottom: 1px solid #eee; padding-bottom: 30px; }
.post-item-wrapper {
display: flex;
gap: 20px;
justify-content: space-between;
align-items: flex-start;
}
.post-text-side {
flex: 1;
}
.post-text-side h2 {
margin-top: 0;
margin-bottom: 10px;
font-size: 22px;
}
.post-text-side h2 a {
color: #333;
}
.post-text-side h2 a:hover {
color: #0066cc;
}
.clean-snippet {
margin: 0;
color: #666;
font-size: 14px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.post-thumb-side {
width: 200px;
flex-shrink: 0;
}
.post-thumb {
width: 100%;
height: 130px;
object-fit: cover;
display: block;
border-radius: 6px;
}
/* [모바일대응] 반응형 미디어 쿼리 */
@media (max-width: 768px) {
.post-item-wrapper {
flex-direction: column-reverse;
gap: 15px;
}
.post-thumb-side {
width: 100%;
}
.post-thumb {
height: 200px;
}
.post-text-side h2 {
font-size: 18px;
}
}
/* 3편에서 추가된 css */
/*
함께 보면 좋은 글 박스 스타일
*/
.related-posts-box {
margin: 40px 0;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 6px;
background-color: #f9f9f9;
}
.related-posts-box h3 {
margin-top: 0;
margin-bottom: 15px;
font-size: 1.1rem;
color: #333;
}
/* Flex를 활용한 리스트 구조 */
.related-list {
display: flex;
flex-direction: column; /* 세로로 차곡차곡 쌓임 */
gap: 10px; /* 아이템 사이 간격 */
margin: 0;
padding: 0;
list-style: none;
}
.related-item {
display: flex;
flex: 1; /* 영역을 유연하게 꽉 채움 */
}
.related-item a {
display: flex;
flex: 1;
padding: 12px 15px;
background-color: #ffffff;
border: 1px solid #eaeaea;
border-radius: 4px;
color: #555;
text-decoration: none;
transition: all 0.2s ease;
}
/* 마우스 올렸을 때 깔끔한 피드백 효과 */
.related-item a:hover {
background-color: #f1f5f9;
border-color: #cbd5e1;
color: #000;
}
.related-title {
font-size: 0.95rem;
font-weight: 500;
}
]]></b:skin>
</head>
<body>
<header>
<b:if cond='data:view.isHomepage'>
<h1 class='blog-title'><a expr:href='data:blog.homepageUrl'><data:blog.title/></a></h1>
<b:else/>
<div class='blog-title'><a expr:href='data:blog.homepageUrl'><data:blog.title/></a></div>
</b:if>
<div class='search-box'>
<form action='/search' method='get'>
<input name='q' placeholder='검색어를 입력하세요...' type='text'/>
<button type='submit'>검색</button>
</form>
</div>
</header>
<main id='main-body'>
<b:section id='content' showaddelement='yes'>
<b:widget id='Blog1' locked='true' title='Blog Posts' type='Blog' maxResults='10'>
<b:includable id='main'>
<b:loop values='data:posts' var='post'>
<b:if cond='data:view.isMultipleItems'>
<article>
<div class='post-item-wrapper'>
<div class='post-text-side'>
<h2><a expr:href='data:post.url'><data:post.title/></a></h2>
<p class="clean-snippet">
<data:post.snippets.long/>
</p>
</div>
<b:if cond='data:post.featuredImage'>
<div class='post-thumb-side'>
<a expr:href='data:post.url'>
<img class='post-thumb' expr:src='data:post.featuredImage' expr:alt='data:post.title'/>
</a>
</div>
</b:if>
</div>
</article>
<b:else/>
<article>
<header class='post-title' aria-label='게시글 제목 영역'><h1><data:post.title/></h1></header>
<div class='post-body'><data:post.body/></div>
</article>
<div class='related-posts-box'>
<h3>함께 보면 좋은 글</h3>
<b:if cond='data:post.labels'>
<b:loop values='data:post.labels' var='label' index='i'>
<b:if cond='data:i == 0'>
<ul class='related-list' expr:id='"related-from-" + data:post.id'/>
<script>
fetch('/feeds/posts/summary/-/' + encodeURIComponent('<data:label.name/>') + '?alt=json&max-results=5')
.then(res => res.json())
.then(data => {
let html = '', currentId = '<data:post.id/>';
(data.feed.entry || []).forEach(entry => {
let id = entry.id.$t.split('post-')[1];
if (id !== currentId) {
let link = entry.link.find(l => l.rel === 'alternate').href;
html += `<li class='related-item'><a href="${link}"><span class='related-title'>${entry.title.$t}</span></a></li>`;
}
});
document.getElementById('related-from-' + currentId).innerHTML = html || "<li class='no-related'>연관된 다른 글이 없습니다.</li>";
});
</script>
</b:if>
</b:loop>
<b:else/>
<p class='no-related'>관련 라벨이 없습니다.</p>
</b:if>
</div>
</b:if>
</b:loop>
<b:if cond='data:view.isMultipleItems'>
<div class='blog-pager'>
<b:if cond='data:newerPageUrl'>
<a class='pager-btn pager-newer' expr:href='data:newerPageUrl'>이전 글</a>
</b:if>
<b:if cond='data:olderPageUrl'>
<a class='pager-btn pager-older' expr:href='data:olderPageUrl'>다음 글</a>
</b:if>
</div>
</b:if>
</b:includable>
</b:widget>
</b:section>
</main>
<footer>
<b:section id='footer' showaddelement='yes' />
</footer>
</body>
</html>




