워드프레스 테마 만들기 – 2

워드프레스 테마의 기본 파일 구조와 각 파일의 역할에 대해 1편에서 자세히 알아보았다. 이제는 이 파일들이 실제로 어떻게 상호작용하며 콘텐츠를 보여주는지, 그리고 각 파일 안에서 어떤 핵심적인 코드들이 사용되는지 좀 더 깊이 파고들어 볼 차례이다.

테마 만들기는 단순히 파일을 준비하는 것을 넘어, 워드프레스의 작동 원리를 이해하고 그에 맞춰 코드를 작성하는 과정이다.

이번 2편에서는 다음과 같은 내용들을 중심으로 워드프레스 테마 제작의 핵심을 짚어볼까 한다.

1. 워드프레스 템플릿 계층 구조

1편에서 index.php가 가장 기본적인 파일이고, 특정 상황에서는 single.phppage.php가 사용된다고 설명했다.

워드프레스는 사용자가 특정 페이지를 요청했을 때, 어떤 템플릿 파일(single.php, page.php, category.php, archive.php 등)을 사용해야 할지 결정하는 내부적인 규칙을 가지고 있다.

이것을 템플릿 계층 구조(Template Hierarchy)라고 부른다.

워드프레스는 요청받은 페이지의 유형(예: 단일 글, 특정 카테고리 페이지, 검색 결과 페이지)에 따라 미리 정해진 순서대로 해당 템플릿 파일을 찾는다.

예를 들어, ‘여행’ 카테고리의 특정 글을 클릭했다면, 워드프레스는 다음과 같은 순서로 파일을 찾아본다.

  1. single-{post_type}-{slug}.php (예: single-post-hello-world.php)
  2. single-{post_type}.php (예: single-post.php)
  3. single.php
  4. index.php

가장 먼저 찾은 파일을 사용하여 페이지를 보여주는 것이다. 만약 single-post.php가 있다면 single.php 대신 그 파일을 사용하게 된다.

이 템플릿 계층 구조를 이해하는 것은 매우 중요하다. 왜냐하면 각 페이지 유형에 맞는 가장 구체적인 템플릿 파일을 만들어야 원하는 레이아웃과 디자인으로 콘텐츠를 표시할 수 있기 때문이다.

모든 페이지를 index.php 하나로 처리할 수도 있지만, 그렇게 하면 단일 글 페이지와 아카이브 페이지의 디자인을 다르게 만들기 어렵다.

single.php, page.php, archive.php 등을 각각 만드는 이유가 바로 이 템플릿 계층 구조 덕분에 각기 다른 유형의 페이지를 맞춤형으로 보여줄 수 있기 때문이다.

2. The Loop 루프

1편의 코드 예제에서 index.php, single.php, page.php, archive.php, search.php 파일들에 공통적으로 등장하는 부분이 있었다.

바로 if ( have_posts() ) : while ( have_posts() ) : the_post(); ... endwhile; else : ... endif; 이 구조이다. 이것이 워드프레스에서 콘텐츠(글, 페이지 등)를 가져와 화면에 표시하는 가장 핵심적인 부분인 The Loop (루프) 이다.

  • have_posts(): 현재 페이지를 표시하기 위해 워드프레스가 데이터베이스에서 가져온 콘텐츠(하나 또는 여러 개)가 있는지 확인하는 함수이다. 콘텐츠가 있다면 true, 없다면 false를 반환한다.
  • the_post(): 루프가 실행될 때마다 호출되어, 워드프레스가 가져온 콘텐츠 목록 중 다음 콘텐츠 하나를 현재 콘텐츠로 설정하는 함수이다. 이 함수가 호출되어야 비로소 the_title(), the_content() 등 현재 콘텐츠의 정보를 가져오는 다른 함수들을 사용할 수 있게 된다.

루프 안에서는 현재 콘텐츠의 제목, 내용, 작성자, 날짜, 카테고리 등 다양한 정보를 가져와 표시하는 템플릿 태그(Template Tags)들을 사용한다. 몇 가지 주요 템플릿 태그는 다음과 같다.

  • the_title(): 현재 콘텐츠의 제목을 표시한다.
  • the_permalink(): 현재 콘텐츠의 고유 주소(URL)를 표시한다. 보통 <a href=""> 태그와 함께 사용하여 링크를 만든다.
  • the_content(): 현재 콘텐츠의 전체 내용을 표시한다. 글 본문 전체가 출력된다.
  • the_excerpt(): 현재 콘텐츠의 요약(발췌문)을 표시한다. 글 목록 페이지 등에서 사용하면 페이지 로딩 속도도 빨라지고 깔끔하게 여러 글을 보여줄 수 있다.
  • the_date(): 현재 콘텐츠의 작성 날짜를 표시한다.
  • the_author(): 현재 콘텐츠의 작성자 이름을 표시한다.
  • the_category(): 현재 콘텐츠가 속한 카테고리 목록을 표시한다.
  • the_tags(): 현재 콘텐츠에 연결된 태그 목록을 표시한다.
  • post_class(): 현재 콘텐츠(article 또는 div 태그)에 콘텐츠 유형, 카테고리, 태그 등에 따른 CSS 클래스를 자동으로 추가한다. 스타일링에 매우 유용하다.
  • comments_template(): 해당 콘텐츠의 댓글 영역(comments.php)을 불러온다. (single.php에서 주로 사용한다.)

예를 들어, 루프 안에서 글 제목과 내용을 표시하려면 다음과 같이 코드를 작성한다.

<?php if ( have_posts() ) : ?>
    <?php while ( have_posts() ) : the_post(); ?>

        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
            <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2> <!-- 제목과 링크 -->
            <div class="entry-meta"> <!-- 메타 정보 -->
                <?php the_date(); ?> by <?php the_author(); ?>
            </div>
            <div class="entry-content">
                <?php the_content(); ?> <!-- 내용 전체 -->
            </div>
        </article>

    <?php endwhile; ?>
<?php endif; ?>

아카이브 페이지에서 글 목록을 요약 형태로 보여준다면 the_content() 대신 the_excerpt()를 사용할 수 있다.

<?php if ( have_posts() ) : ?>
    <?php while ( have_posts() ) : the_post(); ?>

        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
            <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2> <!-- 제목과 링크 -->
            <div class="entry-meta"> <!-- 메타 정보 -->
                <?php the_date(); ?> by <?php the_author(); ?>
            </div>
            <div class="entry-excerpt">
                <?php the_excerpt(); ?> <!-- 내용 요약 -->
                <a href="<?php the_permalink(); ?>">더 읽어보기</a>
            </div>
        </article>

    <?php endwhile; ?>
<?php endif; ?>

이처럼 루프와 템플릿 태그는 워드프레스 테마에서 콘텐츠를 가져와 원하는 형태로 표시하는 가장 기본적인 메커니즘이다.

3. 템플릿 파트(get_template_part)

1편에서 index.php, single.php, page.php 예제 코드 안에 get_template_part() 함수가 등장했다. 예를 들어, get_template_part( 'template-parts/content', get_post_type() ); 와 같은 형태였다.

이 함수는 특정 코드 조각을 별도의 파일로 분리해 두고, 필요할 때마다 불러와 사용하는 워드프레스의 기능이다.

분리된 파일들을 템플릿 파트(Template Parts)라고 부른다.

템플릿 파트를 사용하는 주된 이유는 다음과 같다.

  • 코드 재활용: 글 목록(index.php, archive.php, search.php)과 단일 글(single.php) 페이지에서 모두 글 하나의 정보를 보여주는 방식은 비슷할 수 있다. 이때 글 하나를 표시하는 코드를 template-parts/content.php 같은 파일에 담아두면, 여러 템플릿 파일에서 이 코드를 반복해서 작성할 필요 없이 get_template_part() 함수 호출 하나로 불러와 사용할 수 있다.
  • 코드 구조화 및 가독성 향상: 길고 복잡한 템플릿 파일을 작은 부분들(예: 콘텐츠 본문, 댓글 영역, 글 메타 정보)로 나누어 별도의 파일에 저장하면, 각 템플릿 파일의 코드가 훨씬 짧고 깔끔해진다. 이는 코드의 가독성을 높이고 유지보수를 용이하게 한다.
  • 쉬운 오버라이드(Override): 차일드 테마(Child Theme)에서 부모 테마의 특정 부분만 수정하고 싶을 때, 템플릿 파트 파일만 복사하여 수정하면 부모 테마 파일 전체를 복사할 필요 없이 원하는 부분만 변경할 수 있다.

get_template_part( 'template-parts/content', 'page' ); 와 같이 사용하면 워드프레스는 template-parts 폴더 안에서 content-page.php 파일을 먼저 찾고, 만약 없다면 content.php 파일을 찾아서 불러온다.

이를 통해 페이지 유형별로 조금씩 다른 콘텐츠 표시 방법을 구현하면서도 기본적인 구조는 재활용할 수 있다.

예를 들어, template-parts/content-post.php 파일 안에는 단일 글 페이지에서 사용될 글 제목, 메타 정보, 본문, 태그, 카테고리 등을 표시하는 루프 안의 코드가 들어갈 수 있다.

<?php
/**
 * Template part for displaying posts
 *
 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/
 *
 * @package My_First_WordPress_Theme
 */
?>

<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
    <header class="entry-header">
        <?php
        if ( is_singular() ) : // 단일 페이지/글일 경우 h1으로 제목 표시
            the_title( '<h1 class="entry-title">', '</h1>' );
        else : // 아카이브 등 목록 페이지일 경우 h2로 제목 링크 표시
            the_title( '<h2 class="entry-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></h2>' );
        endif;

        if ( 'post' === get_post_type() ) : // 글(post) 유형일 경우 메타 정보 표시
            ?>
            <div class="entry-meta">
                <?php
                echo '작성일: ' . get_the_date(); // 날짜 표시
                echo ' | 작성자: ' . get_the_author_posts_link(); // 작성자 링크 표시
                ?>
            </div><!-- .entry-meta -->
        <?php endif; ?>
    </header><!-- .entry-header -->

    <div class="entry-content">
        <?php
        the_content( sprintf(
            wp_kses(
                /* translators: %s: Name of current post. Only visible to screen readers */
                __( 'Continue reading<span class="screen-reader-text"> "%s"</span>', 'my-first-theme' ),
                array(
                    'span' => array(
                        'class' => array(),
                    ),
                )
            ),
            wp_kses_post( get_the_title() )
        ) );

        wp_link_pages( array( // 페이지가 나눠져 있을 경우 페이지 링크 표시
            'before' => '<div class="page-links">' . esc_html__( 'Pages:', 'my-first-theme' ),
            'after'  => '</div>',
        ) );
        ?>
    </div><!-- .entry-content -->

    <footer class="entry-footer">
        <?php // 카테고리, 태그 등 표시 로직 추가 가능 ?>
    </footer><!-- .entry-footer -->
</article><!-- #post-<?php the_ID(); ?> -->

이처럼 get_template_part()를 활용하면 템플릿 파일의 코드가 훨씬 간결해지고 관리하기 쉬워진다.

4. 메뉴와 위젯, 사용자 편의 기능 활성화

1편 functions.php에서 register_nav_menus() 함수로 메뉴 위치를 등록하고, register_sidebar() 함수로 위젯 영역을 등록했다.

이제 이렇게 등록된 메뉴와 위젯 영역을 테마의 원하는 위치에 실제로 표시하는 방법에 대해 알아보겠다.

  • 메뉴 표시하기 (wp_nav_menu): header.php 예제에서 보았듯이, 등록된 메뉴 위치에 사용자가 워드프레스 관리자 화면에서 설정한 메뉴를 가져와 표시할 때는 wp_nav_menu() 함수를 사용한다.
// header.php 또는 메뉴를 표시하고 싶은 템플릿 파일 안에서
<nav id="site-navigation" class="main-navigation">
    <?php
    wp_nav_menu(
        array(
            'theme_location' => 'menu-1', // functions.php에서 등록한 메뉴 위치 ID
            'menu_id'        => 'primary-menu', // 생성될 ul 태그의 id 속성
            'container'      => false, // 메뉴를 감싸는 div 태그 생성 여부 (false로 하면 ul 태그만 생성)
            // 다양한 추가 옵션이 있다.
        )
    );
    ?>
</nav>

theme_location 파라미터에 functions.php에서 register_nav_menus() 함수로 등록한 고유 ID(menu-1 등)를 지정하면, 해당 위치에 사용자가 할당한 메뉴가 출력된다.

  • 위젯 표시하기 (dynamic_sidebar): sidebar.php 예제에서 보았듯이, 등록된 위젯 영역에 사용자가 관리자 화면에서 추가한 위젯들을 표시할 때는 dynamic_sidebar() 함수를 사용한다.
// sidebar.php 또는 위젯 영역을 표시하고 싶은 템플릿 파일 안에서
<?php
if ( is_active_sidebar( 'sidebar-1' ) ) : // functions.php에서 등록한 위젯 영역 ID가 활성화(위젯이 추가)되었는지 확인
?>
    <aside id="secondary" class="widget-area">
        <?php dynamic_sidebar( 'sidebar-1' ); // 'sidebar-1' 위젯 영역에 추가된 위젯들을 모두 표시 ?>
    </aside><!-- #secondary -->
<?php endif; ?>

dynamic_sidebar() 함수에 functions.php에서 register_sidebar() 함수로 등록한 위젯 영역의 고유 ID(sidebar-1 등)를 지정하면, 해당 영역에 추가된 위젯들이 차례대로 출력된다. is_active_sidebar() 함수로 해당 위젯 영역이 활성화되었는지 먼저 확인하는 것이 일반적이다.

메뉴와 위젯 기능을 테마에 추가하면 사용자가 관리자 화면에서 웹사이트의 탐색 구조나 사이드바 내용을 쉽게 변경할 수 있게 되어 테마의 유연성이 크게 향상된다.

5. 조건부 태그, 페이지 유형에 따라 다르게 표시하기

워드프레스는 현재 사용자가 보고 있는 페이지가 어떤 유형인지 판단할 수 있는 다양한 조건부 태그(Conditional Tags)를 제공한다.

이 함수들을 활용하면 특정 페이지에서만 특정 요소를 표시하거나, 페이지 유형에 따라 다른 스타일이나 내용을 적용하는 등 동적인 테마를 만들 수 있다.

몇 가지 유용한 조건부 태그는 다음과 같다.

  • is_front_page(): 현재 페이지가 ‘정적인 첫 페이지’로 설정된 페이지이거나 또는 ‘최신 글 목록’이 첫 페이지로 설정된 경우(index.php가 사용되는 경우) true를 반환한다.
  • is_home(): 현재 페이지가 ‘최신 글 목록’ 페이지인 경우 true를 반환한다. (index.php 또는 home.php가 사용된다.) 설정에 따라 is_front_page()와 동시에 true일 수 있다.
  • is_single(): 현재 페이지가 단일 글(Post)을 표시하는 페이지인 경우 true를 반환한다. (첨부 파일이나 사용자 정의 포스트 타입의 단일 페이지에서는 true가 아닐 수 있다.) 특정 ID나 슬러그의 글인지 확인할 수도 있다. (is_single('17'), is_single('hello-world'))
  • is_page(): 현재 페이지가 ‘페이지(Page)’를 표시하는 페이지인 경우 true를 반환한다. 특정 ID나 슬러그, 템플릿 파일의 페이지인지 확인할 수도 있다. (is_page('about'), is_page(5), is_page_template('custom-template.php'))
  • is_category(): 현재 페이지가 카테고리 아카이브 페이지인 경우 true를 반환한다. 특정 ID나 슬러그의 카테고리인지 확인할 수도 있다. (is_category('travel'), is_category(7))
  • is_tag(): 현재 페이지가 태그 아카이브 페이지인 경우 true를 반환한다.
  • is_archive(): 현재 페이지가 모든 종류의 아카이브 페이지(카테고리, 태그, 작성자, 날짜, 사용자 정의 포스트 타입 아카이브 등)인 경우 true를 반환한다.
  • is_search(): 현재 페이지가 검색 결과 페이지인 경우 true를 반환한다.
  • is_404(): 현재 페이지를 찾을 수 없는 404 오류 페이지인 경우 true를 반환한다.
  • is_admin(): 현재 사용자가 워드프레스 관리자 화면을 보고 있는 경우 true를 반환한다.

이 조건부 태그들은 PHP의 if 문과 함께 사용하여 특정 상황에만 코드를 실행하도록 제어할 수 있다. 예를 들어, 첫 페이지에서만 특정 배너를 보여주고 싶다면 <?php if ( is_front_page() ) { // 배너 코드 } ?> 와 같이 작성한다. 또는 글 목록 페이지에서는 글 요약만 보여주고, 단일 글 페이지에서는 글 전체 내용을 보여주도록 템플릿 파트 안에서 조건부 태그를 사용할 수도 있다. (위 template-parts/content-post.php 예제의 is_singular() 사용 참고)

6. 차일드 테마(Child Theme)

만약 다른 사람이 만든 테마를 기반으로 웹사이트를 만들거나, 직접 만든 테마라도 나중에 워드프레스나 다른 플러그인, 혹은 테마 자체의 업데이트가 있을 때 내가 수정한 코드가 사라지는 것을 방지하고 싶다면 차일드 테마를 사용해야 한다.

차일드 테마는 기존 테마(부모 테마)의 기능과 스타일을 그대로 상속받으면서, 내가 원하는 부분만 안전하게 수정하거나 추가할 수 있게 해주는 강력한 기능이다.

차일드 테마를 사용하면 부모 테마 파일은 건드리지 않기 때문에, 나중에 부모 테마가 업데이트되어도 내 수정사항이 그대로 유지된다.

차일드 테마를 만드는 과정은 비교적 간단하다.

  1. wp-content/themes 폴더 안에 부모 테마 폴더와 다른 이름의 새 폴더를 만든다. (예: my-first-theme-child)
  2. 새로 만든 폴더 안에 style.css 파일을 생성하고, 파일 상단에 차일드 테마 정보와 부모 테마의 Template 정보를 필수로 기재한다.
  3. 새로 만든 폴더 안에 functions.php 파일을 생성하고, 부모 테마의 스타일시트를 올바르게 불러오는 코드를 추가한다.

차일드 테마의 style.css 예제:

/*
Theme Name: 내 첫 워드프레스 차일드 테마
Theme URI: http://내사이트주소.com/my-first-theme-child/
Description: 내 첫 워드프레스 테마의 차일드 테마입니다.
Author: 내 이름
Author URI: http://내사이트주소.com/
Template: my-first-theme // *** 부모 테마 폴더 이름 (필수!) ***
Version: 1.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: my-first-theme-child
*/

/* 여기에 차일드 테마의 스타일을 작성한다. */
/* 부모 테마의 스타일을 덮어쓰거나 새로운 스타일 추가 */

차일드 테마의 functions.php 예제 (부모 테마 스타일시트 로드):

<?php
/**
 * My First WordPress Child Theme functions and definitions
 *
 * @link https://developer.wordpress.org/themes/basics/theme-functions/
 *
 * @package My_First_WordPress_Child_Theme
 */

/**
 * Enqueue scripts and styles.
 */
function my_first_theme_child_scripts() {
    wp_enqueue_style( 'my-first-theme-style', get_template_directory_uri() . '/style.css' ); // 부모 테마의 style.css 로드
    wp_enqueue_style( 'my-first-theme-child-style', get_stylesheet_uri(), array('my-first-theme-style'), '1.0' ); // 차일드 테마의 style.css 로드 (부모 스타일시트에 의존)

    // 필요하다면 차일드 테마만의 JavaScript 파일도 여기서 로드한다.
    // wp_enqueue_script( 'my-first-theme-child-script', get_stylesheet_directory_uri() . '/js/custom-child.js', array('jquery'), '1.0', true );
}
add_action( 'wp_enqueue_scripts', 'my_first_theme_child_scripts' );

// 여기에 부모 테마 함수를 오버라이드하거나 새로운 함수를 추가한다.
// 부모 테마의 functions.php 파일 코드를 직접 복사해서 붙여넣는 것은 지양해야 한다.

차일드 테마에서 부모 테마의 특정 템플릿 파일(예: single.php)을 수정하고 싶다면, 부모 테마 폴더에서 해당 파일(single.php)을 차일드 테마 폴더로 복사해온 후 차일드 테마 폴더 안의 파일을 수정하면 된다.

워드프레스는 템플릿 파일을 찾을 때 차일드 테마 폴더를 먼저 살펴보기 때문에, 같은 이름의 파일이 차일드 테마에 있으면 부모 테마의 파일 대신 차일드 테마의 파일을 사용한다.

차일드 테마는 워드프레스 테마를 다루는 개발자나 사용자에게 있어 거의 필수적인 개념이다. 안전하고 효율적인 테마 관리를 위해 꼭 익혀두어야 한다.


이번 2편에서는 워드프레스가 템플릿 파일을 선택하는 원리인 템플릿 계층 구조, 콘텐츠를 불러와 화면에 표시하는 핵심적인 The Loop템플릿 태그들, 코드 재활용을 위한 템플릿 파트, 그리고 테마 수정 및 관리를 위한 차일드 테마에 대해 자세히 알아보았다.

다음 편에서는 아마도 좀 더 실용적인 테마 제작 과정, 예를 들어 디자인 구현을 위한 CSS/JavaScript 연동이나 테마 커스터마이저 활용, 혹은 워드프레스 개발 환경 설정 등에 대해 다룰 수 있을 것이다.