diff --git a/src/components/FluxNote.vue b/src/components/FluxNote.vue
index 82f4a2c..723ac87 100644
--- a/src/components/FluxNote.vue
+++ b/src/components/FluxNote.vue
@@ -1,5 +1,5 @@
diff --git a/src/hooks/useMarkdownPostRender.hook.ts b/src/hooks/useMarkdownPostRender.hook.ts
new file mode 100644
index 0000000..9e1a486
--- /dev/null
+++ b/src/hooks/useMarkdownPostRender.hook.ts
@@ -0,0 +1,54 @@
+import { nextTick, type Ref, toValue, watch } from "vue"
+
+import { useImages } from "@/hooks/useImages.hook"
+import {
+ runMermaid,
+ runTikz,
+ useShikiji
+} from "@/hooks/useMarkdown.hook"
+
+interface MarkdownPostRenderOptions {
+ onReady?: () => void
+ tikz?: boolean
+ mermaid?: () => boolean
+ shikiji?: () => boolean
+ images?: () => string | null | undefined
+ triggers?: Ref[]
+}
+
+export const useMarkdownPostRender = (
+ contentRef: Ref,
+ scopeSelector: () => string,
+ options: MarkdownPostRenderOptions = {}
+) => {
+ const sources = [contentRef, ...(options.triggers ?? [])]
+
+ watch(
+ sources,
+ async () => {
+ if (!toValue(contentRef)) return
+ await nextTick()
+ options.onReady?.()
+
+ const scope = scopeSelector()
+
+ if (options.tikz) {
+ void runTikz(`${scope} .tikz`)
+ }
+
+ if (options.mermaid?.()) {
+ runMermaid(`${scope} .mermaid`)
+ }
+
+ if (options.shikiji?.()) {
+ void useShikiji()
+ }
+
+ const imagesSha = options.images?.()
+ if (imagesSha) {
+ useImages(imagesSha)
+ }
+ },
+ { immediate: true }
+ )
+}
diff --git a/src/views/PublicNoteView.vue b/src/views/PublicNoteView.vue
index ddbd0a0..1c0fb53 100644
--- a/src/views/PublicNoteView.vue
+++ b/src/views/PublicNoteView.vue
@@ -1,7 +1,7 @@