Next.jsで作ったブログにStyleを適用していく

published: 2020/9/7 update: 2021/11/5

Table of Contents

TL;DR

前回の記事で、markdown をうまくレンダリングできるようになったので、次は Style を適用していく。適用すべき対象は、最初の記事に書いたように、

  • material-ui
  • Prism.jsでのcode syntax
  • amp-mathmlによる数式
  • Github markdown css

である。AMP対応するには鬼門である。

Styleの適用

Prism.js && Github markdown css

prism.js公式サイトからcssをダウンロードしておく。github-markdown-cssからダウンロードする。github-markdown-cssは自動生成なので!importantとかが使われていてAMPに対応できないのでそのへんは除いてしまう。

Next.js 12になって、_document.jsでcssをロードすると怒られるようになってしまった。 仕方ないので、現状はcssをjsのstringとして保存しておいて、componentでimportし、sytled-jsxで表現している。

css.js
export const css = `
  css
`

上のようなファイルを作成しておき

component.jsx
import {css} from "css.js"

export default function Component() {
  return (
    <>
      <div>styled</div>
      <style jsx>{css}</style>
    </>
  )
}

componentで読み込んで、そのままsytled-jsxに突っ込むことで、一応対応できている。

2021/07/01改稿 Next.js v11

webpack5を使っているとasset modulesを使うことでraw-loaderの機能が実装できる。まずはnext.config.jsに設定を書く。フルAMPなので、cssをimportすることは想定していない。

next.config.js
module.exports = {
  webpack(config, options) {
    config.module.rules.push({
      test: /\.css/,
      resourceQuery: /raw/,
      type: 'asset/source'
    })
    return config
  },
}

これでcssファイルをraw-loaderのように読み込める。

_document.js
import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheets } from "@material-ui/core/styles";

import theme from "@libs/theme";

// @ts-ignore
import css from "../styles/github_markdown.css?raw";
// @ts-ignore
import prismCss from "../styles/prism.css?raw";
// @ts-ignore
import globalCss from "../styles/global.css?raw";

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="ja">
        <Head>
          {/* PWA primary color */}
          <meta name="theme-color" content={theme.palette.primary.main} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
  const sheets = new ServerStyleSheets();
  const originalRenderPage = ctx.renderPage;

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
    });

  const initialProps = await Document.getInitialProps(ctx);

  return {
    ...initialProps,
    // Styles fragment is rendered after the app and page rendering finish.
    styles: [
      ...React.Children.toArray(initialProps.styles),
      <style
        key="custom"
        dangerouslySetInnerHTML={{
          __html: `${globalCss}\n${css}\n${prismCss}`,
        }}
      />,
      sheets.getStyleElement(),
    ],
  };
};
2020/9/7 raw-loaderを使った実装そのあと、raw-loaderを使ってcssを_app.tsxでimportして、直接埋め込む。できるならMarkdownのページだけで読み込みたいが...

ちょっとmaterial-ui成分も入ってしまっているが、_document.jsは以下の感じ。

_document.js
import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheets } from "@material-ui/core/styles";

import theme from "@libs/theme";

// @ts-ignore
import css from "!!raw-loader!../styles/github_markdown.css";
// @ts-ignore
import prismCss from "!!raw-loader!../styles/prism.css";
// @ts-ignore
import globalCss from "!!raw-loader!../styles/global.css";

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="ja">
        <Head>
          {/* PWA primary color */}
          <meta name="theme-color" content={theme.palette.primary.main} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
  const sheets = new ServerStyleSheets();
  const originalRenderPage = ctx.renderPage;

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
    });

  const initialProps = await Document.getInitialProps(ctx);

  return {
    ...initialProps,
    // Styles fragment is rendered after the app and page rendering finish.
    styles: [
      ...React.Children.toArray(initialProps.styles),
      <style
        key="custom"
        dangerouslySetInnerHTML={{
          __html: `${globalCss}\n${css}\n${prismCss}`,
        }}
      />,
      sheets.getStyleElement(),
    ],
  };
};

custom loaderでrefactorを使ってcodeをTokenに落とす作業をしておけばAMPでもコードがハイライトされる。順番の関係か、prismjsはダーク系のテーマにしたのに黒くならなかったので、github-markdown-css側で背景を黒にしておいた。

example

const MOD: usize = 1e9 as usize + 7;
>>> import pandas as pd
>>> pd.read_csv("/path/to/file.csv")

amp-mathml

KatexはAMPに対応できない。

そこで、まず、remark-mathを使って、mathinlineMathのノードに変換する。その後、custom loaderを使って、type === "math"type === "inlineMath"に対応するamp-mathmlを埋め込む。インラインの数式はparagraphのchildrenなので注意が必要。

example

インライン数式

普通の数式

material-ui

2021/09/23

yarn add @mui/material @mui/styles @mui/lab @mui/icons-material @emotion/react @emotion/styled @emotion/server

Material-UIのversionが上がったので、色々設定が必要になった。Emotionベースなので、AMP対応する場合は注意が必要。@emotion/serverextractCriticalToChunksを使うのが重要らしい(参考)。

というのは、サーバーサイドレンダリングをnext.jsでするときに、CSSの読み込みがリセットされてしまうことがあるらしい(参考)。実際に自分の画面でも崩れていて、結構時間を溶かした。 幸いなことに、material-uiの公式がテンプレート例を作成してくれている(javascript, typescript)。これを参考にしながら_app.tsx_document.tsxを書き換えておく。あとnext.jsのリンクとmaterial-uiのリンクもclassNameの問題とかでうまく行かないことがあるので、Linkコンポーネントを作っておく。

!importantを使用しているコンポーネントは使用できないので注意が必要になる。

感想

スタイルの適用はこんな感じ。しかし、material-uiは結構がっつりcssっぽいものを触らないとだめで結構難しい。Bootstrapはだいたいよしなにやってくれていたので、css力が本当に無い。

記事に間違い等ありましたら、お気軽に以下までご連絡ください

E-mail: illumination.k.27|gmail.com ("|" replaced to "@")

Twitter: @illuminationK

当HPを応援してくれる方は下のリンクからお布施をいただけると非常に励みになります。

Ofuse

Other Articles

Site Map

Table of Contents

    TL;DR

    Styleの適用

      Prism.js && Github markdown css

      amp-mathml

      material-ui

    感想


当HPを応援してくれる方は下のリンクからお布施をいただけると非常に励みになります。

Ofuse
Privacy Policy

Copyright © illumination-k 2020-2022.