(2022/03/03 追記)
(2023/10/05 追記)
はじめに
ブログを
- 適度な
距離 - メンテナンス性
- 高速性と
拡張性 - 無
広告
1. 適度な 距離
あらゆる
2. メンテナンス性
先人た
3. 高速性と 拡張性
Next.js。
4. 広告や 統計の 排除
過剰な
機能一覧と 実装
以下は
Markdownの 処理
(2022/12/28 更新)
next-mdx-remoteで
getStaticProps
(/pages
のfetch
/ cache
(/app
の
記事メタデータの 取得
---slug: "blog-renewal"title: "Next.jsでブログをつくった"date: "20220326"tags: ["tech", "web", "nextjs"]---
GitHub Flavored Markdown
| just a | table || ------ | ----- || 1 | 2 || 3 | 4 |
https://www.haxibami.net
- [x] トゥードゥー- [ ] リストや、脚注[^1]も書ける
[^1]: 脚注だよ〜
just a | table |
---|---|
1 | 2 |
3 | 4 |
- トゥードゥー
- リストや、
脚注1も 書ける
絵文字
:v:
が
数式
適当な
$$( \sum*{k=1}^{n} a_k b_k )^2 \leq ( \sum*{k=1}^{n} {a*k}^2 ) ( \sum*{k=1}^{n} {b_k}^2 )$$
$e^{i\pi} + 1 = 0$ :arrow_left: インライン数式
⬅️ インライン数式
ルビ
> 昨日午後、\{†聖剣†|エクスカリバー\}を振り回す\{全裸中年男性|無敵の人\}が出現し……
昨日午後、
†聖剣†を 振り回す全裸 中 年男性が 出現し……
ページ内の 見出しの リンク
:arrow_right: [はじめに](#はじめに)に飛べるよ
➡️
Mermaid Diagram
(2023/05/07 更新)
rehype-mermaidjsを
```mermaidsequenceDiagramAlice->>John: Hello John, how are you?loop Healthcheck John->>John: Fight against hypochondriaendNote right of John: Rational thoughts!John-->>Alice: Great!John->>Bob: How about you?Bob-->>John: Jolly good!```
```mermaidstateDiagram-v2 state fork_state <<fork>> [*] --> fork_state fork_state --> State2 fork_state --> State3
state join_state <<join>> State2 --> join_state State3 --> join_state join_state --> State4 State4 --> [*]```
```mermaidpie"Dogs" : 386"Cats" : 85"Rats" : 15```
シンタックスハイライト
rehype-pretty-codeを
リンクカード
こういう
上の
- 文書中に
単独で 貼られた リンクの Node
を検出 - リンク先に
アクセスして メタデータ ( title
、description
、og image
)を取得 - これらの
情報を Node
の 属性に 付加し、 独自要素 (例: <linkcard>
)に置き換え - 独自要素を、
MDXの 処理系 ( next-mdx-remote
)側で 自作コンポーネントに 置換
する
import LinkCard from "components/LinkCard";import NextImage from "components/NextImage";import NextLink from "components/NextLink";
import type { LinkCardProps } from "components/LinkCard";import type { NextImageProps } from "components/NextImage";import type { NextLinkProps } from "components/NextLink";import type { MDXComponents } from "mdx/types";
type ProvidedComponents = MDXComponents & { a: typeof NextLink; img: typeof NextImage; linkcard: typeof LinkCard;};
const replaceComponents = { a: (props: NextLinkProps) => <NextLink {...props} />, img: (props: NextImageProps) => <NextImage {...props} />, linkcard: (props: LinkCardProps) => <LinkCard {...props} />,} as ProvidedComponents;
export default replaceComponents;
import MDXComponent from "components/MDXComponent";
const result = compileMDX({ source, components: MDXComponent,});
画像処理
Markdown内の
import { getPlaiceholder } from "plaiceholder";import { visit } from "unist-util-visit";
import type { Image } from "mdast";import type { Plugin, Transformer } from "unified";import type { Node } from "unist";
const rehypeImageOpt: Plugin<[void]> = function imageOpt(): Transformer { return async (tree: Node) => { const promises: (() => Promise<void>)[] = []; visit(tree, "image", (node: Image) => { const src = node.url;
promises.push(async () => { const blur = await getPlaiceholder(src); node.data = { hProperties: { src: blur.img.src, width: blur.img.width, height: blur.img.height, aspectRatio: `${blur.img.width} / ${blur.img.height}`, blurDataURL: blur.base64, }, }; }); }); await Promise.allSettled(promises.map((t) => t())); };};
export default rehypeImageOpt;
ダークモード
外部
Open Graph画像の 生成
(2022/05/07 更新)
別記事を
サイトマップ生成
(2023/05/07 更新)
別記事を
フィード対応
Feedと
import fs from "fs";
import { Feed } from "feed";
import { dateConverter } from "./lib/build.js";import { SITEDATA } from "./lib/constant.js";import { getPostsData } from "./lib/fs.js";
// variablesconst HOST = "https://www.haxibami.net";
// generate feedconst feedGenerator = async () => { const author = { name: "haxibami", link: HOST, };
const date = new Date(); const feed = new Feed({ title: SITEDATA.blog.title, description: SITEDATA.blog.description, id: HOST, link: HOST, language: "ja", image: `${HOST}/kripcat.jpg`, favicon: `${HOST}/favicon.ico`, copyright: `All rights reserved ${date.getFullYear()}, ${author.name}`, updated: date, feedLinks: { rss2: `${HOST}/rss/feed.xml`, json: `${HOST}/rss/feed.json`, atom: `${HOST}/rss/atom.xml`, }, author: author, });
const blogs = await getPostsData("articles/blog");
blogs.forEach((post) => { const url = `${HOST}/blog/posts/${post.data?.slug}`; feed.addItem({ title: `${post.data?.title}`, description: `${post.preview}`, id: url, link: url, guid: url, date: new Date(dateConverter(post.data?.date)), category: post.data?.tags ? post.data?.tags.map((tag) => ({ name: tag, })) : [], enclosure: { url: encodeURI( `${HOST}/api/ogp?title=${post.data?.title}&date=${post.data?.date}.png`, ), length: 0, type: "image/png", }, }); });
fs.mkdirSync("public/rss", { recursive: true }); await Promise.all([ fs.promises.writeFile( "public/rss/feed.xml", feed.rss2().replace(/&/g, "&"), ), fs.promises.writeFile("public/rss/atom.xml", feed.atom1()), fs.promises.writeFile("public/rss/feed.json", feed.json1()), ]);};
const GenFeed = () => { return new Promise<void>((resolve) => { feedGenerator(); resolve(); });};
export default GenFeed;
感想
最高!