2024-12-14
ブログ記事の TEX 対応に苦戦していたが、CSR(クライアントサイドレンダリング)により解決した。
npm install remark-math rehype-katex
import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; import 'katex/dist/katex.min.css';
'use client'; // マークダウンをレンダリング import ReactMarkdown from 'react-markdown'; // TEX レンダリング import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; import 'katex/dist/katex.min.css'; import remarkGfm from 'remark-gfm'; // リンクを文字列ではなくリンク形式でレンダリングするために使用 // 独自 CSS import styles from './markdown.module.css'; const Markdown = (props: { content: string }) => { // マークダウンファイルの中身 const { content } = props; return ( <div className={styles.content}> <ReactMarkdown remarkPlugins={[remarkMath, remarkGfm]} rehypePlugins={[rehypeKatex]} components={{ // リンクを別タブで開く a: ({ node, ...props }) => ( <a {...props} target="_blank" rel="noopener noreferrer"> {props.children} </a> ), }} > {content} </ReactMarkdown> </div> ); }; export default Markdown;
import Markdown from '@/components/markdown/page'; import styles from './post.module.css'; export default async function Page({ params }: { params: { id: string } }) { const res = await fetch(`${process.env.BACKEND_URL}/api/posts/${params.id}`, { cache: 'no-store', }); const post = await res.json(); if (!post) { return <div>ページが見つかりません</div>; } return ( <div className={styles.main}> <h1 className={styles.title}>{post.title}</h1> <p className={styles.date}>{post.date}</p> {/* 改修前は以下のように SSR でマークダウンファイルを HTML 化したものを dangerouslySetInnerHTML に渡していた */} {/* <div className={styles.content} dangerouslySetInnerHTML={{ __html: post.content }} // HTMLをレンダリング /> */} {/* 今回の改修では以下のように CSR でマークダウンファイルの中身を受け取り ReactMarkdown でレンダリングするようにした */} <Markdown content={post.content} /> </div> ); }
// 以下は SSR から CSR に変更するため削除した import { marked } from 'marked'; app.get('/api/posts/:id', (req, res) => { const postId = req.params.id; const filePath = path.join(postsDirectory, `${postId}.md`); if (fs.existsSync(filePath)) { const fileContents = fs.readFileSync(filePath, 'utf8'); const { data, content } = matter(fileContents); // 以下も削除した const htmlContent = marked(content, { renderer }); // 上記の htmlContent ではなくそのまま content を返すように修正した res.json({ id: postId, ...data, content: content }); } else { res.status(404).send({ error: 'Post not found' }); } });
インライン表示:
ブロック表示: