단일 글 화면에 관련 글 세 개를 붙였습니다. 그런데 그 아래의 댓글 영역이 다른 글의 것을 보여 주고, 브레드크럼도 엉뚱한 글을 가리킵니다. 템플릿을 아무리 들여다봐도 이상한 곳이 없습니다. 원인은 빠뜨린 한 줄입니다.
the_post() 는 전역 상태를 바꿉니다
워드프레스 템플릿 태그(the_title() · the_permalink() · get_the_ID())는 인자를 받지 않습니다. 대신 전역 $post 를 읽습니다. 그리고 루프 안의 the_post() 가 하는 일이 바로 그 전역을 다음 글로 바꿔 놓는 것입니다.
보조 쿼리를 돌리면 그 안의 the_post() 가 같은 전역을 건드립니다. 루프가 끝나면 전역은 보조 쿼리의 마지막 글을 가리킨 채로 남습니다. 원래 글로 돌려놓지 않으면 그 아래 코드는 전부 다른 글을 그리게 되고, 증상은 “템플릿이 이상하다” 로 나타납니다.
wp_reset_postdata() 는 전역을 메인 쿼리의 현재 글로 되돌립니다. 보조 루프 바로 뒤에 두고, 습관적으로 함께 씁니다.
<?php
$related = new WP_Query( [
'post_type' => 'post',
'posts_per_page' => 3,
'post__not_in' => [ get_the_ID() ],
'no_found_rows' => true,
] );
if ( $related->have_posts() ) :
while ( $related->have_posts() ) :
$related->the_post();
get_template_part( 'template-parts/card', 'note' );
endwhile;
wp_reset_postdata(); // ← 이 한 줄이 없으면 아래가 전부 틀어진다
endif;
query_posts() 는 쓰지 않습니다
비슷해 보이는 함수가 하나 더 있습니다. query_posts() 는 보조 쿼리를 만드는 것이 아니라 메인 쿼리 자체를 갈아 끼웁니다. 그 결과 페이지네이션이 깨지고, is_single() 같은 조건 태그가 거짓말을 시작하며, 쿼리가 한 번 더 실행됩니다.
목록에 나오는 글을 바꾸고 싶다면 답은 pre_get_posts 입니다. 메인 쿼리를 실행되기 전에 수정하므로 추가 쿼리가 없고 조건 태그도 그대로 유효합니다. 단 가드가 필수입니다.
<?php
add_action( 'pre_get_posts', function ( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return; // ← 없으면 관리자 목록과 보조 쿼리까지 바뀐다
}
if ( $query->is_category() ) {
$query->set( 'posts_per_page', 12 );
}
} );
보조 쿼리를 가볍게 만드는 인자들
보조 쿼리는 화면마다 실행되므로 기본값을 그대로 쓰면 필요 없는 일을 합니다. 상황에 맞게 꺼 두면 됩니다.
posts_per_page => -1 이 특히 위험합니다. 개발 중에는 글이 열 개뿐이라 잘 돌아가고, 콘텐츠가 쌓인 뒤에 운영에서만 느려집니다. 상한을 정하면 최악의 경우가 예측 가능해집니다.
같은 이유로 루프 안에서 또 쿼리를 도는 구조를 피합니다. 글 하나마다 쿼리가 하나씩 늘어나므로, 목록이 길어질수록 비용이 곱으로 커집니다.
쿼리와 성능의 관계를 더 다루는 글은 성능 최적화 아카이브에 있고, 실제 사이트에서 어떤 순서로 원인을 좁혀 가는지는 작업 과정에 공개돼 있습니다.
다음 회차
여기까지가 만드는 이야기였습니다. 남은 두 회차는 덜어 내는 이야기입니다. 다음 회차는 부모 테마가 싣는 자산 중 우리가 쓰지 않는 것을 내리는 절차와, 그때 함께 넘어오는 책임을 다룹니다.