Skip to content

Commit 606c036

Browse files
committed
初記事書いた
1 parent 6c79c5e commit 606c036

27 files changed

+992
-84
lines changed

articles/202012290656_75234_markdown-style-test.mdx

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ tags: [mdx, markdown, blog]
2121
2. test2
2222
3. test3
2323

24-
```js live=true
24+
```js live=true data-file=script.js
2525
console.log('live coding')
2626
```
2727

@@ -36,14 +36,16 @@ discord.jsにおいて、テーブルをinlineに表示したい場合は`inline
3636

3737
> Botのいるチャンネルにoverwatch `<battletag> <region> <platform>` という形での投稿がされた時に指定されたプレイヤーの名前、レベル、レートをBotに返信させるサンプルコードを書いてみます。
3838
39+
Botのいるチャンネルにoverwatch `<battletag> <region> <platform>` という形での投稿がされた時に指定されたプレイヤーの名前、レベル、レートをBotに返信させるサンプルコードを書いてみます。
40+
3941
Overwatchのプレイヤーデータ自体はここから見れるのでスクレイピングして持ってくるのも一つですが、その辺りを代わりにやってくれるoverwatch-api(非公式)というものが提供されてるので利用させてもらいましょう。
4042
今回使用するパッケージをインストールします。
4143

4244
## Code block
4345

4446
consoleに文字列を出力させる
4547

46-
```js
48+
```js test
4749
console.log('test desu')
4850
const test = getPostData()
4951
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
---
2+
title: "Next.jsでブログを作った"
3+
date: "2021-04-04-00-00"
4+
tags: [nextjs, mdx, blog]
5+
---
6+
7+
Next.jsで自分用のブログを作った(このブログ)ので、開発時に工夫した事を書こうと思います。
8+
9+
ブログとしての基本的な機能はだいぶ前に出来ていたものの、CSSとかOGP辺りの見た目の部分が面倒で後回しにしていたら年明けるどころか桜咲いてました。
10+
何か明確な用途があったから作ったわけではなくReact周りの技術で何か作りたくて作った感じなので、継続して更新するかは分かりませんが、せっかく作ったからには色々書きたい気持ちです。
11+
12+
13+
## 特徴
14+
- Next.jsでSSG
15+
- MDXで記事を書ける
16+
- YAML Front Matter 対応
17+
- OGP画像の生成
18+
19+
20+
## MDXで記事を書きたい
21+
MDX詳細: https://mdxjs.com/
22+
23+
ブログを作ろうと思った当初は microCMS でコンテンツ管理をするつもりだったんですが、自分だけが更新するブログ程度の規模ならMarkdownファイルをリポジトリに直置きで十分という結論になった。
24+
その後調べているとMarkdown中にJSXが書けるMDXというものを知り、ただのMarkdownよりは面白そうだったので採用。
25+
26+
Next.jsでMDXファイルに記述したコンテンツを表示したい場合、一番簡単なのはNext.jsのルーティングをそのまま利用して`pages`ディレクトリ以下にMDXファイルを配置してしまうことだと思います。
27+
28+
例えば、`next.config.js`[MDX用の設定](https://mdxjs.com/getting-started/next)を記述してから以下のようなMDXファイルを`pages`以下に配置すれば、js/tsファイル等を置いた時と同様にアクセス可能です。
29+
30+
```
31+
// hoge.mdx
32+
33+
import Layout from '../components/Layout.tsx';
34+
35+
# MDX Document
36+
37+
- hogehoge
38+
- some text
39+
40+
export default ({children}) => {
41+
<Layout>
42+
{children}
43+
</Layout>
44+
}
45+
```
46+
47+
ただし、今回は以下の理由により`pages`以下にMDXファイルを直接置くのは避けました。
48+
49+
- MDXファイルには実際に表示するコンテンツ以外の記述(ヘッダーやフッター等の共通部品の記述等)はしたくない
50+
- 記事数が増えた時に`pages`ディレクトリ以下が肥大化するのが何となく気持ち悪かった
51+
- ビルド時にファイル名やFront Matterから記事のメタデータを拾ったりしたかったので、そういう対象のファイルが`pages`以下にあるのは何か違う気がした
52+
53+
54+
回避策として`pages/articles/[slug].ts`で記事ページへのリクエストを受けて、対応する記事を [Dynamic import](https://nextjs.org/docs/advanced-features/dynamic-import) して表示するようにしてます。
55+
56+
MDXの文書そのままでは当然import出来ないのでwebpackのLoaderを通す必要があります。
57+
`next.config.js`を以下の様に編集し、拡張子が`.mdx`のファイルは`@mdx-js/loader`を通します。
58+
59+
```js
60+
// next.config.js
61+
62+
const path = require("path")
63+
const rehypePrism = require('@mapbox/rehype-prism')
64+
65+
module.exports = {
66+
pageExtensions: ['js', 'jsx', 'ts', 'tsx'],
67+
webpack: (config, options) => {
68+
config.module.rules.push({
69+
test: /\.mdx?$/,
70+
use: [
71+
options.defaultLoaders.babel,
72+
{
73+
loader: '@mdx-js/loader',
74+
options: {
75+
rehypePlugins: [rehypePrism]
76+
}
77+
},
78+
path.join(__dirname, "./lib/fm-loader"),
79+
],
80+
})
81+
return config
82+
},
83+
}
84+
```
85+
86+
これでMDXで書いた各記事がReactコンポーネントとしてimport出来るようになったので、`pages/articles/[slug].ts`でDynamic importして使うだけです。
87+
88+
89+
```jsx
90+
//
91+
92+
import Layout from '../../components/Layout'
93+
import dynamic from 'next/dynamic'
94+
import { GetStaticProps, GetStaticPaths } from 'next'
95+
96+
const Article = (props: Props) => {
97+
// Dynamic import
98+
const MDX = dynamic(() => import(`../../articles/${props.fileName}`))
99+
100+
return (
101+
<Layout>
102+
<MDX />
103+
</Layout>
104+
)
105+
}
106+
107+
export const getStaticPaths: GetStaticPaths = async () => {
108+
// ...省略
109+
}
110+
111+
export const getStaticProps: GetStaticProps = async ({ params }) => {
112+
// ...省略
113+
}
114+
115+
export default Article
116+
```
117+
118+
### コードのシンタックスハイライト
119+
シンタックスハイライターには [Prism.js](https://prismjs.com/) を使いました。
120+
121+
先程の`next.config.js`を見ると`@mdx-js/loader`のoptionsに [rehype-prism](https://github.com/mapbox/rehype-prism) が指定されていますが、これはMDXのLoaderを実行したタイミングでコードブロックのHTMLを Prism.js の形式に変換するためです。
122+
123+
Prism.jsを使う場合ブラウザ側でスクリプトを読み込んで変換するのが一般的な使い方だと思いますが、ビルド時に変換してしまったほうが効率的です。
124+
125+
そうすれば後は好きなPrism.jsのテーマCSSを読み込めば色付けされます。
126+
127+
(テーマは公式サイトで見れるものの他に[GitHub](https://github.com/PrismJS/prism-themes)にもいくつかある)
128+
129+
130+
## 記事のメタ情報をFront Matterで書きたい
131+
132+
タグや作成日、タイトル等の各記事のメタデータをYAML Front Matterで書きたかった。
133+
134+
YAML Front Matterってこういうやつです。
135+
136+
```
137+
// some.md
138+
139+
---
140+
title: "タイトル"
141+
date: "2021-04-05-12-37"
142+
tags: [blog, markdown, diary]
143+
---
144+
145+
# Markdown document
146+
- some text
147+
- some text 2
148+
```
149+
150+
MDXでFront Matterを使う方法は色々あると思うんですが、今回は記事ページ以外の色々なページで記事のメタ情報を使いたかったので、Next.jsのビルド直前に以下のようなスクリプトを実行して記事のメタ情報をまとめたJSONファイルを生成し、その後のNext.jsのSSGのプロセスでメタ情報が欲しい時は生成したJSONファイルから取ってくるようにしました。
151+
152+
153+
```js
154+
// generate-json.mjs (一例)
155+
156+
import matter from 'gray-matter'
157+
158+
// fileNamesには記事のMDXファイル名が配列で入っている想定
159+
const articleList = fileNames.map(fileName => {
160+
const fullPath = path.join(articlesDir, fileName)
161+
const doc = fs.readFileSync(fullPath, 'utf8')
162+
const frontMatter = matter(doc).data
163+
return { ...frontMatter }
164+
})
165+
const json = JSON.stringify(articleList, undefined, 2)
166+
fs.writeFileSync(path.join(process.cwd(), 'gen/articles.json'), json)
167+
```
168+
169+
`package.json`を、Next.jsのビルドコマンド等を実行する前に特定のスクリプトを割り込ませるように書き換えた)
170+
171+
```json
172+
// package.json
173+
174+
{
175+
"name": "blog",
176+
"version": "0.1.0",
177+
"private": true,
178+
"scripts": {
179+
"dev": "node script/generate-json.mjs && next dev",
180+
"build": "node script/generate-json.mjs && next build && next export",
181+
"start": "next start"
182+
},
183+
// ...省略
184+
}
185+
```
186+
187+
若干力技な感じもしていて、もう少しいい方法無いかなーとも思ったんですが、とりあえずJSONに吐き出しておけば再利用もしやすいのでまあ良いかという気持ちです。
188+
189+
余談ですがこの記事書く時に Front Matter の正しい名称が分からなくて困った。
190+
人によって「YAML Front Matter」だったり「Frontmatter」だったり「Front-matter」だったりと表記ゆれが激しくて、結局今もどれが正しいのか分かってないです(特に統一されてない...?)。
191+
192+
193+
194+
## OGP画像を自動生成したい
195+
![OGP画像](/ogp/38226.png)
196+
node-canvasを使って記事タイトルから上のようなOGP画像を生成してます。
197+
198+
本当はビルド時に自動で画像生成スクリプトを実行出来れば良かったんですが、デプロイ先のVercelで node-canvas が動かなかったので、とりあえずは都度ローカルでスクリプトを実行して画像生成してからpushする運用をしています。
199+
200+
参考: https://mizchi.dev/202006211925-support-ogp
201+
202+
203+
204+
# まとめ
205+
- 個人的にはそれなりに使いやすいものが出来たと思ってる
206+
- Next.jsを触ってみて結構楽しかったので、もっと掘り下げて触ってみたい
207+
- よく分かってなかった`webpack`の雰囲気を知れたのは良かった
208+
- CSSはあまり気に入ってないので改善必須
209+
- 冗長な処理が多い気がするので見直したい

components/Article.tsx

+19-17
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,26 @@ export default function Article(props: {
1515
return (
1616
<>
1717
<h1 className={styles.title}>{props.title}</h1>
18-
<div className={styles.info}>
19-
<Link href={blogConfig.twitterUrl}>
20-
<a className={styles.authorIcon}>
21-
<Image
22-
src='/img/icon.png'
23-
alt={blogConfig.name}
24-
width={38}
25-
height={38}
26-
className={styles.authorIconImg}
27-
/>
28-
</a>
29-
</Link>
30-
<Link href={blogConfig.twitterUrl}>
31-
<a className={styles.authorName}>@{blogConfig.name}</a>
32-
</Link>
33-
<div className={styles.date}>{formatDate(props.date)}</div>
18+
<div className={styles.articleDetails}>
19+
<div className={styles.info}>
20+
<Link href={blogConfig.twitterUrl}>
21+
<a className={styles.authorIcon}>
22+
<Image
23+
src='/img/icon.png'
24+
alt={blogConfig.name}
25+
width={33}
26+
height={33}
27+
className={styles.authorIconImg}
28+
/>
29+
</a>
30+
</Link>
31+
<Link href={blogConfig.twitterUrl}>
32+
<a className={styles.authorName}>@{blogConfig.name}</a>
33+
</Link>
34+
<div className={styles.date}>{formatDate(props.date)}</div>
35+
</div>
36+
<Tags tags={props.tags} />
3437
</div>
35-
<Tags tags={props.tags} />
3638
<article className={styles.article}>
3739
{props.children}
3840
</article>

components/ArticleList.tsx

+4-10
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,12 @@ export default function ArticleList(props: {
1616
{/* <div className={styles.spacer}></div> */}
1717
{props.articles.map((a, index) => {
1818
return (
19-
<div className={styles.item} key={index}>
20-
{/* <div className={styles.date}>{formatDate(a.date)}</div> */}
21-
<Link href='/articles/[slug]' as={`/articles/${a.slug}`}>
22-
{/* <Link href={`/articles/${a.slug}`}> */}
23-
<a className={styles.title}>{a.title}</a>
24-
</Link>
25-
<div className={styles.articleInfo}>
19+
<Link href='/articles/[slug]' as={`/articles/${a.slug}`}>
20+
<div className={styles.item} key={index}>
2621
<div className={styles.date}>{formatDate(a.date)}</div>
27-
<div className={styles.separation}>/</div>
28-
<TagsInArticleList tags={a.tags} />
22+
<div className={styles.title}>{a.title}</div>
2923
</div>
30-
</div>
24+
</Link>
3125
)
3226
})}
3327
</>

0 commit comments

Comments
 (0)