Next.js × docker-compose × microCMS × VercelでJAMstackブログを作る - 02
前回の続きです。your-project-name
your-app-name
という名前は適宜変えて読んでください。
JAMstackブログを作る
1.日付のフォーマット
詳細ページを見ると、2022-01-01T15:00:00.000Z
といった感じで日付が表示されています。
日付データはISO 8601形式のUTC(協定世界時)にて返却しています。
ご利用の際にはフロントエンド側にて現地時間への変換が必要です。(日本時間はプラス9時間)
https://help.microcms.io/ja/knowledge/specification-of-utc-time
とのことなので、日時をフォーマットできるjsライブラリのdate-fns
をインストールし変換していきます。
(公式でdayjs紹介されていたのは終わってから気づきました)
インストールの仕方は前回のmicrocms-js-sdk
のインストール方法と同じです。
your-project-name
$ docker-compose stop #コンテナ起動していたらこのコマンドでコンテナ停止
$ docker-compose run --rm your-app-name /bin/sh #コンテナを一時的に起動し中に入る
=====ここからコンテナ内=====
# ls #ディレクトリ内を確認 docker-compose.yml、Dockerfile、your-app-nameがあると思います
# cd your-app-name #your-app-nameディレクトリに入る
# npm install date-fns #date-fnsをインストール
# exit #コンテナを抜ける
=====ここまでコンテナ内=====
libs/date.js
を作成します。yyyy.MM.dd
がフォーマット後のレイアウトなのでお好みで変えてください。
/your-app-name/libs/date.js
import { parseISO, format } from 'date-fns'
export default function Date({ dateString }) {
const date = parseISO(dateString)
return <time dateTime={dateString}>{format(date, 'yyyy.MM.dd')}</time>
}
[id].js
の編集をします。
/your-app-name/pages/blog/[id].js
import { client } from "../../libs/client";
import Date from "../../libs/date"; //追加
〜省略〜
<main>
<h1>{blog.title}</h1>
<p><Date dateString={blog.date} /></p> //Dateで囲む
日付が2022.01.01
の形式になっていたら成功です🎉
2. Sassのインストールとスタイルの当て方
Next.jsは面倒な設定無しにsassが使えるようなので、sassを導入していきます。
インストールの仕方は先程やった通り。
# npm install sass
ちなみにチュートリアルにある--save
はなくてもpackage.json
に記述されるようなので省いてます。
これで.scssが使えるようになりました。
Next.jsではCSS Modulesという考え方が推されているようなので、これに沿って設計していきます。BEM×Sassばっかりだったのでよくわからない新鮮です。
まずはサイト全体で読み込みたいスタイル=グローバルレベルのスタイルを書いてみます。styles
ディレクトリにglobal
ディレクトリ、その中にglobal.scss
を作成します。もともとある.cssファイルは削除してOK。
/styles/global/global.scss
body {
background-color: #f0f0f7;
}
全ページで使用されるレイアウトは/pages/_app.js
なので、ここでglobal.scss
を読み込みます。
/pages/_app.js
import '../styles/global/global.scss' //scss読み込み
サイト全体に背景色がついたらOKです👏
次にコンポーネントレベルのスタイルを書いてみます。styles
ディレクトリにindex.module.scss
を作成します。もともとあったHomeでもいいんですが名前揃わなかったのが違和感だったので…
/styles/index.module.scss
.list {
font-weight: bold;
}
.item {
+ .item {
margin-top: 2.5rem;
}
> a {
display: block;
padding: 2rem;
background: #fff;
}
}
/pages/index.js
を編集します。
/pages/index.js
import styles from '../styles/index.module.scss'; //scss読み込み
〜省略〜
<ul className={styles.list}> //classを追加
{blog.map((blog) => (
<li key={blog.id} className={styles.item}> //classを追加
最初の一行でindex.module.scss
を読み込みstyles
という名前で定義、className={styles.classname}
でclassをつけていきます。
一覧ページで各記事のリンクが白ベタになっていたらOKです👏
3. レイアウトの作成
ページの作りやスタイルの当て方についてある程度わかったところで、レイアウトを整えていきます。
ヘッダー、フッターは共通パーツになるのでそれぞれコンポーネントとして作成、それをすべてのコンテンツをラップするレイアウトコンポーネントを作成して配置していきます。
your-app-nameディレクトリ直下にcomponents
ディレクトリ、その中にHeader.js
とFooter.js
を作成します。
/your-app-name/components/Header.js
import Link from "next/link";
import styles from '../styles/header.module.scss';
export default function Header() {
return (
<header className={styles.header}>
<h1 className={styles.title}>
<Link href="/">
<a>
Blog title
</a>
</Link>
</h1>
</header>
)
}
/your-app-name/components/Footer.js
import styles from '../styles/footer.module.scss';
export default function Footer() {
return (
<footer className={styles.footer}>
<small>Copyright © Blog title All Rights Reserved.</small>
</footer>
)
}
続けてLayout.js
を作成します。
/your-app-name/components/Layout.js
import Header from './Header'
import Footer from './Footer'
export default function Layout({ children }) {
return (
<>
<Header />
<main>
{children}
</main>
<Footer />
</>
)
}
_app.js
を編集します。
/pages/_app.js
import Layout from '../components/Layout' //追加
function MyApp({ Component, pageProps }) {
return (
<Layout> //Layoutで囲む
<Component {...pageProps} />
</Layout>
);
}
レイアウトで囲んだことで、コンテンツがラップされ、ヘッダーとフッターが表示されるようになりました👏
scssがなくてページがエラーになっているので、scssを作成していきます。
4. CSSの構造、作成
sassの変数やmixin、reset.cssは全体で使用したいので、global
ディレクトリに入れてみることにします。styles
ディレクトリはこんな感じに。reset.css
はDestyle.cssを入れました。
styles
├── global
│ ├── _mixin.scss //メディアクエリなどよく使うmixin
│ ├── _variables.scss //変数
│ ├── common.scss //上記のファイルの橋渡し的存在
│ ├── global.scss //レイアウトと要素型セレクターの指定 bobyとか
│ └── reset.css //リセットcss
├── detail.module.scss //詳細ページ
├── footer.module.scss //フッター
├── header.module.scss //ヘッダー
└── index.module.scss //一覧ページ
構造・管理方針についてはCSS Moduleのベストプラクティスがまだよくわかってないので今後の課題…reset.css
は_app.js
で読み込みます。
/pages/_app.js
import '../styles/global/reset.css' //追加
変数などの使い方はこんな感じにしてみました。Dart sass対応✍
/styles/global/_variables.scss
// カラーなどの変数の指定
$color-primary: #6667AB;
$color-secondary: #DDDDEC;
$color-bg: #F0F0F7;
$color-font: #1F2037;
$color-font-thin: #ACACBE;
/styles/global/_mixin.scss
// mixinの設定
$breakpoint: (
sp: "screen and (max-width: 767px)",
pc: "screen and (min-width: 768px)",
);
@mixin mediaquery($device) {
@media #{map-get($breakpoint, $device)} {
@content;
}
}
/styles/global/common.scss
// @forwardで各scss読み込み
@forward "variables";
@forward "mixin";
header.module.scss
などの中ではこのように記述します。
/styles/header.module.scss
@use "global/common" as common; //呼び出し元の宣言と名前定義
.header {
padding: 1.5rem 2.5rem;
font-size: 1.8em;
font-weight: bold;
color: #fff;
background-color: common.$color-primary;
@include common.mediaquery(sp) {
padding: 1rem 1.4rem;
font-size: 1.4em;
}
}
common.scss
を通じて、各変数、mixinを呼び出せています👏
後は各ページのjsにscss読み込み、HTML部分を修正・追加しつつ、cssをガシガシ書いていきます💪
5. headの設定(title設定、Googlefontの読み込み)
titleなどのheadも_app.jsで設定してしまいます。
/pages/_app.js
import Head from "next/head"; //追加
function MyApp({ Component, pageProps }) {
return (
<Layout>
<Head> //追加
<title>Blog title</title> //titleタグ
<link //Googlefont読み込み
href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP〜"
rel="stylesheet"
/>
</Head>
<Component {...pageProps} />
</Layout>
);
}
export default MyApp
6. 詳細ページのスタイル
詳細ページでmicroCMSから受け取った本文にスタイルを当てる方法です。
microCMSで、各装飾を使った記事を一つ書いておくとやりやすいかもしれません。[id].js
とdetail.module.scss
はこんな状態です。
/pages/blog/[id].js
import { client } from "../../libs/client";
import Date from "../../libs/date";
import styles from '../../styles/detail.module.scss';
export default function BlogId({ blog }) {
return (
<div>
<div className={styles.header}>
<p className={styles.date}><Date dateString={blog.date} /></p>
<h1 className={styles.title}>{blog.title}</h1>
</div>
<div
className={styles.body}
dangerouslySetInnerHTML={{
__html: `${blog.body}`,
}}
/>
</div>
);
}
〜省略〜
/styles/detail.module.scss
@use "global/common" as common;
.header {
margin-bottom: 2.5rem;
font-weight: bold;
.date {
margin-bottom: 0.6rem;
color: common.$color-secondary;
font-size: 1rem;
}
.title {
font-size: 2rem;
line-height: 1.6;
}
}
.body {
padding: 2rem;
border-radius: 5px;
background-color: #fff;
box-shadow: 0 13px 24px 0 rgba(102, 103, 171, 5%);
}
ページを検証ツールなどで見てみると、.detail_body__XXXXの下層にh1タグやpタグ、blockquoteタグで内容が出力されていることがわかります。
なので子セレクタや子孫セレクタで装飾していきます。以下は例です。
/styles/detail.module.scss
@use "global/common" as common;
〜省略〜
.body {
padding: 2rem;
border-radius: 5px;
background-color: #fff;
box-shadow: 0 13px 24px 0 rgba(102, 103, 171, 5%);
& > h1, h2, h3, h4, h5 {
font-weight: bold;
margin: 2em auto 0.5rem;
&:first-child {
margin-top: 0;
}
}
& > h1 {
font-size: 1.875rem; //30px h2〜以下お好みで
}
& > p {
line-height: 1.8;
letter-spacing: 0.2px;
& > code {
background-color: common.$color-bg;
border-radius: 3px;
padding: 0.1rem 0.2rem;
color: #ff668a;
margin: 0 0.1rem;
}
}
& a {
color: common.$color-font-thin;
text-decoration: underline;
}
& img {
max-width: 100%;
height: auto;
}
& > blockquote {
position: relative;
padding: 1rem;
border-left: common.$color-font-thin 0.3rem solid;
color: common.$color-font-thin;
z-index: 1;
& > a {
margin-top: 1rem;
text-decoration: underline;
text-underline-offset: 0.2rem;
@include common.hover;
}
}
& > pre {
margin: 0.5rem 0;
line-height: 1.5;
border-radius: 5px;
overflow-x: auto;
}
& > ol, ul {
list-style-position: inside;
> li + li {
margin-top: 0.6rem;
}
}
& > ol {
list-style-type: decimal;
}
& > ul {
list-style-type: disc;
> li {
> ul {
list-style: circle;
list-style-position: inside;
padding-left: 1rem;
margin-top: 0.5rem;
> li + li {
margin-top: 0.3rem;
}
}
}
}
}
ここを設定するとグッとブログっぽくなります✨
7. シンタックスハイライトの導入
コードブロックとして記述したコードに色をつけてわかりやすく(シンタックスハイライト)していきます。
HTMLパーサーのcheerio
とシンタックスハイライトをしてくれるhighlight.js
のjsライブラリをインストールし設定します。
どちらも今までのライブラリと同じくnpmでインストール出来ます。
# npm install cheerio
# npm install highlight.js
[id].js
を編集します。まずはライブラリの読み込み。
/pages/blog/[id].js
import cheerio from 'cheerio'; //追加
import hljs from 'highlight.js'; //追加
import 'highlight.js/styles/monokai.css'; //追加
getStaticProps
にmicroCMSから返されるリッチエディタ部分を処理する部分を追加。
export const getStaticProps = async (context) => {
const id = context.params.id;
const data = await client.get({ endpoint: "blog", contentId: id });
//ここから追加
const $ = cheerio.load(data.body);
$("pre code").each((_, elm) => {
const result = hljs.highlightAuto($(elm).text());
$(elm).html(result.value);
$(elm).addClass("hljs");
});
return {
props: {
blog: data,
highlightedBody: $.html(), //追加
},
};
};
最後にソース部分を編集。
export default function blogId({ blog, highlightedBody }) { //追加
return (
<div>
<div className={styles.header}>
<p className={styles.date}><Date dateString={blog.date} /></p>
<h1 className={styles.title}>{blog.title}</h1>
</div>
<div
className={styles.body}
dangerouslySetInnerHTML={{
__html: highlightedBody, //変更
}}
/>
</div>
);
}
これでコード部分にいい感じに色がついて見やすくなりました✨
カラーテーマはhighlight.js
のデモページから選ぶことができ、importするcss名を変えることで反映されます。
かなり大好きなMarianaがないのが悲しみでした
import 'highlight.js/styles/monokai.css';
このとき、/your-app-name/node_modules/highlight.js/stylesにあるcss名で指定してください。
ただ判別の精度?がよくないのか、たまに意図されない箇所で色が変わってしまっているのが少し難点です。
If automatic detection doesn’t work for you, or you simply prefer to be explicit, you can specify the language manually in the using the class attribute:<pre><code class="language-html">...</code></pre>
“自動検出がうまくいかない場合や、単に明示的であることを好む場合は、class属性を使用して手動で言語を指定することができます。”
https://highlightjs.org/usage/
…とのことで、codeタグに言語名のclassを追加することでその言語でハイライトしてくれるようになるようです。
次回はVercelでホスティングと公開を紹介します。
参考
date-fns
https://note.com/karaage1970/n/ne0970347a9e9
css
https://qiita.com/tetsutaroendo/items/8e3351bc4bfbb419f662
https://zenn.dev/catnose99/scraps/5e3d51d75113d3
レイアウト
https://weseek.co.jp/tech/733/
https://qiita.com/akitxxx/items/277a7be35156c2677110
Googlefont
https://nextjs.org/docs/basic-features/font-optimization
https://penpen-dev.com/blog/next-webfont/
シンタックスハイライト
https://blog.microcms.io/syntax-highlighting-on-server-side/
https://qiita.com/cawauchi/items/ff6489b17800c5676908