본문으로 건너뛰기

플러그인 시스템 API

import {
definePlugin,
createAnalyticsPlugin,
createRSSPlugin,
createCommentsPlugin,
} from "@noxion/core";

import type { NoxionPlugin, PluginFactory, PluginConfig } from "@noxion/core";

definePlugin()

타입 안전한 플러그인 팩토리를 생성합니다. TypeScript 타입 추론을 위한 identity 헬퍼입니다.

시그니처

function definePlugin<Options = unknown, Content = unknown>(
factory: PluginFactory<Options, Content>
): PluginFactory<Options, Content>

예시

import { definePlugin } from "@noxion/core";

const createMyPlugin = definePlugin<{ hideTag?: string }>((options) => {
return {
name: "my-plugin",

transformPosts({ posts }) {
const hideTag = options.hideTag ?? "private";
return posts.filter((post) => !post.metadata.tags?.includes(hideTag));
},
};
});

NoxionPlugin 인터페이스

interface NoxionPlugin<Content = unknown> {
name: string;

// 설정 검증
configSchema?: {
validate(opts: unknown): { valid: boolean; errors?: string[] };
};

// 데이터 훅
loadContent?: () => Promise<Content> | Content;
contentLoaded?: (args: { content: Content; actions: PluginActions }) => Promise<void> | void;
allContentLoaded?: (args: { allContent: AllContent; actions: PluginActions }) => Promise<void> | void;

// 빌드 라이프사이클
onBuildStart?: (args: { config: NoxionConfig }) => Promise<void> | void;
postBuild?: (args: { config: NoxionConfig; routes: RouteInfo[] }) => Promise<void> | void;

// 콘텐츠 변환
transformContent?: (args: { recordMap: ExtendedRecordMap; post: BlogPost }) => ExtendedRecordMap;
transformPosts?: (args: { posts: BlogPost[] }) => BlogPost[];

// SEO / 메타데이터
extendMetadata?: (args: { metadata: NoxionMetadata; post?: BlogPost; config: NoxionConfig }) => NoxionMetadata;
injectHead?: (args: { post?: BlogPost; config: NoxionConfig }) => HeadTag[];
extendSitemap?: (args: { entries: SitemapEntry[]; config: NoxionConfig }) => SitemapEntry[];

// 라우팅
extendRoutes?: (args: { routes: RouteInfo[]; config: NoxionConfig }) => RouteInfo[];

// v0.2 훅
registerPageTypes?: () => PageTypeDefinition[];
onRouteResolve?: (route: RouteInfo) => RouteInfo | null;

/** @deprecated */
extendSlots?: (slots: Record<string, unknown>) => Record<string, unknown>;
}

훅 레퍼런스

transformPosts

호출 시점: 모든 포스트가 Notion에서 페치된 후, ISR 캐싱 전.

용도: 포스트 필터링, 파생 필드 계산 (단어 수, 읽기 시간), 정렬 오버라이드.

transformPosts({ posts }) {
return posts.map(post => ({
...post,
frontmatter: {
...post.frontmatter,
readingTime: estimateReadingTime(post),
},
}));
}

registerPageTypes

호출 시점: 플러그인 초기화 중.

용도: 내장 blog, docs, portfolio 외의 커스텀 페이지 타입 등록.

registerPageTypes() {
return [{
name: "recipe",
defaultTemplate: "recipe/page",
schemaConventions: {
ingredients: { names: ["Ingredients"] },
prepTime: { names: ["Prep Time"] },
},
}];
}

onRouteResolve

호출 시점: 페이지의 URL 생성 시.

용도: 페이지 타입별 URL 패턴 커스터마이징.

onRouteResolve(route) {
if (route.path.startsWith("/recipe/")) {
return { ...route, path: route.path.replace("/recipe/", "/recipes/") };
}
return route;
}

extendSlots

호출 시점: pre-v0.3 테마 슬롯 모델의 레거시 훅.

용도: 하위 호환성 전용. 신규 테마는 컴포넌트를 직접 노출하고 앱 코드에서 조합하는 방식을 권장합니다.

extendSlots(slots) {
return {
...slots,
readingTimeDisplay: "📖 {{readingTime}}",
};
}

configSchema

확인 시점: loadPlugins()에서 플러그인 옵션 검증 시.

configSchema: {
validate(opts: unknown) {
const errors: string[] = [];
if (typeof opts !== "object" || opts === null) {
return { valid: false, errors: ["Options must be an object"] };
}
return { valid: errors.length === 0, errors };
},
},

transformContent

호출 시점: 페이지의 recordMap<NotionPage>에 전달되기 전.

injectHead

호출 시점: 페이지의 <head> 태그 생성 시. 홈페이지와 태그 페이지에서 postundefined.

extendMetadata

호출 시점: 페이지의 Next.js Metadata 생성 시.

extendSitemap

호출 시점: 사이트맵 엔트리 생성 시.


PluginFactory 타입

type PluginFactory<Options = unknown, Content = unknown> = (
options: Options
) => NoxionPlugin<Content>;

설정 가능한 플러그인에 권장되는 패턴:

export const createMyPlugin: PluginFactory<MyOptions> = (options = {}) => {
return {
name: "my-plugin",
configSchema: { validate(opts) { /* ... */ } },
transformPosts({ posts }) { /* ... */ },
};
};

HeadTag 타입

interface HeadTag {
tagName: string;
attributes?: Record<string, string>;
innerHTML?: string;
}

SitemapEntry 타입

interface SitemapEntry {
url: string;
lastmod?: string;
changefreq?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
priority?: number;
}

내장 플러그인 팩토리

createAnalyticsPlugin()

createAnalyticsPlugin({
provider: "google" | "plausible" | "umami" | "custom",
trackingId: string,
customScript?: string,
})

자세한 내용은 애널리틱스 플러그인을 참조하세요.

createRSSPlugin()

createRSSPlugin({
feedPath?: string, // 기본값: "/feed.xml"
limit?: number, // 기본값: 20
})

자세한 내용은 RSS 플러그인을 참조하세요.

createCommentsPlugin()

createCommentsPlugin({
provider: "giscus" | "utterances" | "disqus",
config: { /* 프로바이더별 옵션 */ },
})

자세한 내용은 댓글 플러그인을 참조하세요.