페이지 프래그먼트

이 페이지

page:fragments 훅은 렌더링된 공개 페이지의 head 또는 body에 스크립트나 원시 HTML을 제공합니다. 구조화된 메타데이터로 표현할 수 없는 브라우저 코드, 예를 들어 <noscript> 폴백이 있는 애널리틱스 로더에 사용하세요.

프래그먼트 출력은 퍼스트파티 페이지 코드로 실행됩니다. EmDash는 이 훅을 신뢰할 수 있는 인프로세스 플러그인에만 호출하며, 샌드박스 플러그인은 프래그먼트를 기여하지 않습니다. 페이지에 meta 태그, canonical·alternate 링크, JSON-LD가 필요하면 대신 샌드박스 호환 page:metadata 훅을 사용하세요.

기능과 훅 선언하기

page:fragments에는 네이티브 런타임 정의의 hooks.page-fragments:register가 필요합니다. 다음 훅은 구성된 애널리틱스 ID가 있을 때만 외부 스크립트와 HTML 폴백을 추가합니다.

return definePlugin({
	id: "plugin-analytics",
	version: "0.1.0",
	capabilities: ["hooks.page-fragments:register"],
	hooks: {
		"page:fragments": async (event, ctx) => {
			const analyticsId = await ctx.settings.get<string>("analyticsId");
			if (!analyticsId || event.page.path.startsWith("/_emdash/")) return null;

			const encodedId = encodeURIComponent(analyticsId);
			return [
				{
					kind: "external-script",
					placement: "head",
					src: `https://analytics.example.com/client.js?id=${encodedId}`,
					async: true,
					key: "analytics-client",
				},
				{
					kind: "html",
					placement: "body:end",
					html: "<noscript>Analytics requires JavaScript.</noscript>",
					key: "analytics-fallback",
				},
			];
		},
	},
});

원시 HTML 기여는 그대로 삽입됩니다. 가능하면 프래그먼트를 정적으로 유지하고, 그렇지 않으면 설정·콘텐츠·요청 데이터·외부 서비스에서 올 수 있는 모든 값을 이스케이프하세요.

네이티브 플러그인에서는 definePlugin()에서 기능을 선언합니다. createPlugin()이 런타임 기능 목록을 제공하므로 디스크립터에 두 번째 사본은 필요하지 않습니다.

기능이 없으면 EmDash는 경고를 기록하고 훅을 등록하지 않습니다. 훅 오류는 기록되며 페이지 렌더링을 막지 않습니다.

레이아웃 삽입 지점 추가하기

호스트 테마가 지원할 placement를 선택합니다. 해당하는 EmDash 컴포넌트에 동일한 PublicPageContext를 전달해야 합니다.

  1. 레이아웃에서 페이지 컨텍스트를 한 번 구성합니다.

    ---
    import { createPublicPageContext } from "emdash/page";
    import {
        EmDashBodyEnd,
        EmDashBodyStart,
        EmDashHead,
    } from "emdash/ui";
    
    interface Props {
        title: string;
        description?: string;
        content?: { collection: string; id: string; slug?: string | null };
    }
    
    const { title, description, content } = Astro.props;
    const page = createPublicPageContext({
        Astro,
        kind: content ? "content" : "custom",
        pageType: content ? "article" : "website",
        title,
        description,
        content,
    });
    ---
  2. 지원하는 각 placement를 문서의 해당 위치에 렌더링합니다.

    <html lang="en">
        <head>
            <title>{title}</title>
            <EmDashHead page={page} />
        </head>
        <body>
            <EmDashBodyStart page={page} />
            <slot />
            <EmDashBodyEnd page={page} />
        </body>
    </html>

EmDashHead는 head 프래그먼트와 함께 EmDash 및 플러그인 메타데이터를 렌더링합니다. EmDashBodyStart는 여는 <body> 직후에 body:start 프래그먼트를, EmDashBodyEnd는 페이지 콘텐츠 뒤에 body:end 프래그먼트를 렌더링합니다. 컴포넌트를 생략한 테마는 해당 placement의 프래그먼트를 렌더링하지 않습니다. 필요한 삽입 지점은 플러그인 README에 문서화하세요.

기여 참조

훅은 하나의 기여, 배열 또는 null을 반환할 수 있습니다. 지원되는 기여 형태는 다음과 같습니다.

kind필수 필드선택 필드출력 동작
external-scriptplacement, srcasync, defer, attributes, key<script src="…"> 요소를 렌더링합니다.
inline-scriptplacement, codeattributes, key<script> 요소 안에 code를 렌더링합니다.
htmlplacement, htmlkeyhtml을 살균하지 않고 삽입합니다.

placement는 head, body:start, body:end를 받습니다.

EmDash는 속성 이름과 값을 HTML 이스케이프하고, 이름이 on으로 시작하는 속성을 제거합니다. 인라인 스크립트에서는 </를 이스케이프하여 값이 스크립트 요소를 닫지 못하게 합니다. 이러한 렌더러 검사는 신뢰할 수 없는 HTML이나 JavaScript를 안전하게 만들지 않습니다. 플러그인은 삽입되는 언어와 컨텍스트에 맞게 보간 데이터를 계속 인코딩해야 합니다.

다음 훅은 인라인 스크립트에 JSON 값을 안전하게 배치합니다.

"page:fragments": async (event) => {
	if (event.page.kind !== "content" || !event.page.content) return null;

	return {
		kind: "inline-script",
		placement: "body:start",
		code: `window.currentContent = ${JSON.stringify({
			collection: event.page.content.collection,
			id: event.page.content.id,
		})};`,
		key: "current-content",
	};
},

한 placement 안에서 같은 key의 기여는 첫 번째 값을 유지합니다. 키가 없는 외부 스크립트는 src로도 중복 제거됩니다. 두 훅이 같은 논리 프래그먼트를 설명할 수 있으면 안정적인 키를 사용하세요.

페이지 컨텍스트 참조

훅은 { page }를 받습니다. page 객체는 호스트 레이아웃에서 오며, 요청 중에 로드된 콘텐츠 항목에는 SEO 패널 값을 선택적으로 겹칩니다.

필드타입과 의미
url절대 페이지 URL.
pathURL의 pathname.
locale활성 로케일, 또는 null.
kindEmDash 항목이면 content, 다른 페이지면 custom.
pageType테마 정의 타입. 보통 article 또는 website.
title, pageTitle전체 문서 제목과 선택적 페이지 전용 제목.
description, canonical, image페이지 메타데이터 값. 각각 nullable.
content렌더링된 항목의 선택적 { collection, id, slug } 참조.
seoOpen Graph 제목·설명·이미지·robots의 선택적 덮어쓰기.
articleMeta선택적 게시 시각·수정 시각·작성자.
siteName, siteUrl선택적 공개 사이트 신원과 origin.
breadcrumbs선택적 root-first { name, url } 항목. 빈 배열은 명시적으로 빵 부스러기 없음을 의미합니다.

kind, pageType, path, locale, content로 프래그먼트가 나타나는 위치를 제한하세요. 페이지 컨텍스트가 결정에 필요한 신원을 이미 가지고 있으면 항목을 다시 가져오지 마세요.

가능하면 구조화된 메타데이터를 우선하세요

다음 출력에는 page:metadata를 사용하세요.

  • <meta name="…"> 및 <meta property="…"> 태그
  • canonical, alternate, author, license, nlweb, site.standard.document 링크
  • JSON-LD 그래프

page:metadata는 구조화된 기여를 검증하고, 페이지 기본 메타데이터와 중복을 제거하며, 네이티브와 샌드박스 플러그인 모두에서 동작합니다. 구조화된 훅이 표현할 수 없는 실행 가능 코드나 마크업을 브라우저가 받아야 할 때 page:fragments를 사용하세요.