Skip to content

[Bug] [SEO] Extension Point의 extensionPointProps가 평가되지 않아 게시글 본문이 SEO HTML에 출력되지 않음 #86

Description

@koojunho

증상 요약

SEO 요청(?_escaped_fragment_= 또는 검색 봇 User-Agent)으로 공개 게시글에 접근하면 게시글 본문이 <meta name="description">에는 포함되지만, <body>의 게시글 본문 영역에는 출력되지 않습니다.

SEO 캐시를 삭제하고 X-Seo-Cache: MISS 상태에서도 동일하게 재현됩니다.

재현 절차

  1. 비밀글이 아닌 공개 게시글을 작성합니다.
  2. 일반 브라우저에서 해당 게시글 본문이 정상적으로 출력되는지 확인합니다.
  3. 게시글 주소에 ?_escaped_fragment_=를 붙여 접근합니다.
https://example.com/게시글주소?_escaped_fragment_=
  1. 반환된 HTML 소스에서 게시글 본문을 확인합니다.

기대한 동작

SEO 서버 렌더링 결과의 <body>에도 게시글 본문이 HTML로 출력되어야 합니다.

<body>
    <div id="app">
        <div class="post-content">
            게시글 본문 내용
        </div>
    </div>
</body>

실제 동작

게시글 본문은 메타태그에는 정상적으로 포함됩니다.

<meta name="description" content="게시글 본문 내용">

하지만 <body>의 게시글 본문 영역은 비어 있습니다.

<div></div>

게시글 API 응답은 정상임을 확인했습니다.

is_secret: false
status: published
content: 정상적인 게시글 본문

SEO 캐시를 삭제하고 X-Seo-Cache: MISS 상태에서도 동일하게 발생했습니다.

원인을 확인한 결과, LayoutExtensionService가 Extension Point 호스트의 props를 주입 컴포넌트의 extensionPointProps에 표현식 형태로 전달하지만, ComponentHtmlMapper가 이를 SEO 렌더링 컨텍스트에 추가하지 않고 있었습니다.

주입된 HtmlContent 컴포넌트는 다음 표현식을 사용합니다.

{{extensionPointProps.content ?? ''}}

일반 React 렌더링에서는 정상적으로 처리되지만, SEO PHP 렌더링에서는 extensionPointProps가 평가 컨텍스트에 없어서 빈 문자열로 처리됩니다.

app/Seo/ComponentHtmlMapper.phprenderComponent() 시작 부분에 다음 처리를 추가하면 본문이 정상적으로 출력됩니다.

private function renderComponent(
    array $component,
    array $context,
    ExpressionEvaluator $evaluator
): string {
    $context = $this->resolveExtensionPointContext(
        $component,
        $context,
        $evaluator
    );

    // 기존 렌더링 코드...
}

추가한 메서드는 다음과 같습니다.

/**
 * Extension Point 주입 컴포넌트의 props를 SEO 평가 컨텍스트에 추가합니다.
 */
private function resolveExtensionPointContext(
    array $component,
    array $context,
    ExpressionEvaluator $evaluator
): array {
    $extensionPointProps = $component['extensionPointProps'] ?? null;

    if (! is_array($extensionPointProps)) {
        return $context;
    }

    $context['extensionPointProps'] = $this->resolveExtensionPointValues(
        $extensionPointProps,
        $context,
        $evaluator
    );

    return $context;
}

/**
 * Extension Point props 내부의 표현식을 재귀적으로 해석합니다.
 */
private function resolveExtensionPointValues(
    mixed $value,
    array $context,
    ExpressionEvaluator $evaluator
): mixed {
    if (is_string($value) && str_contains($value, '{{')) {
        return $evaluator->evaluate($value, $context);
    }

    if (is_array($value)) {
        foreach ($value as $key => $nestedValue) {
            $value[$key] = $this->resolveExtensionPointValues(
                $nestedValue,
                $context,
                $evaluator
            );
        }
    }

    return $value;
}

수정 후 게시글 본문이 메타태그뿐 아니라 SEO HTML의 <body>에도 정상적으로 출력되는 것을 확인했습니다.

환경 정보 (필수)

  • 코어 버전: 7.0.5
  • 업그레이드 이슈인 경우 — 업그레이드 전 버전 → 후 버전: 해당 없음, 신규 설치
  • PHP 버전: PHP 8.5.8
  • 웹서버·실행 방식: FrankenPHP, Caddy 'php_server', FrankenPHP 내장 PHP 실행 방식
  • DB: MySQL 8.4.10
  • OS: Windows + WSL2, Ubuntu 26.04 LTS / 컨테이너 내부 Debian 13

Apache, Nginx, mod_php, PHP-FPM, php artisan serve는 사용하지 않습니다.

활성 확장 목록 (해당 시)

해당 없음.

코어 SEO 렌더러인 app/Seo/ComponentHtmlMapper.php에서 재현되는 문제입니다.

커스텀 자산 여부 (해당 시)

  • modules/_bundled/·plugins/_bundled/·templates/_bundled/ 아래에 직접 추가한 커스텀 확장이 있습니다.
  • public/.htaccess에 직접 추가한 규칙이 있습니다.
  • 그 외 코어 배포본에 없는 커스텀 파일을 직접 추가/수정했습니다.

문제는 순정 상태에서 재현됐으며, 원인 확인을 위해 다음 파일을 수정했습니다.

app/Seo/ComponentHtmlMapper.php

위 수정 적용 후 정상 동작하는 것을 확인했습니다.

스크린샷·로그

SEO 응답 헤더:

X-Seo-Cache: MISS

확인 결과:

- 일반 브라우저 렌더링: 게시글 본문 정상 출력
- 게시글 API: content 정상
- is_secret: false
- status: published
- SEO meta description: 게시글 본문 포함
- SEO body: 게시글 본문 영역이 빈 <div></div>로 출력
- ComponentHtmlMapper 수정 후: SEO body에 게시글 본문 정상 출력

추가 정보

일반 화면에서는 React 렌더러가 Extension Point를 통해 전달된 extensionPointProps를 정상적으로 처리합니다.

SEO 요청에서는 PHP의 ComponentHtmlMapper가 같은 레이아웃 JSON을 HTML로 변환하지만, extensionPointProps를 평가 컨텍스트에 추가하는 과정이 빠져 있어 일반 렌더링과 SEO 렌더링 결과가 달라지는 것으로 보입니다.

다음 회귀 테스트가 추가되면 좋겠습니다.

  1. extensionPointProps.content가 SEO HTML에 출력되는지 확인
  2. 중첩된 extensionPointProps 표현식이 정상적으로 평가되는지 확인
  3. 일반 React 렌더링과 SEO PHP 렌더링이 동일한 본문 데이터를 출력하는지 확인

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions