Open11

Next.jsを触り始めたので学びをまとめていく

あしやひろあしやひろ

開発者ツールのconsoleで以下のエラーが出ていた。

  • Warning: Expected server HTML to contain a matching <a> in <a>.
  • Warning: validateDOMNesting(...): <a> cannot appear as a descendant of <a>.

原因はこれだった

import { Link } from "@chakra-ui/react"
import NextLink from "next/link"

<Link color="blue.500" textDecoration="underline">
  <NextLink href="/about">...</NextLink>
</Link>

Next.js の Link を使いたいけれど、スタイルは Chakra-UI のものを当てたいのでこのようにしていたけれど、レンダリングのHTMLが <a><a>...</a></a> というように入れ子になってしまっていた。

なので、chakraの Link のほうを <Link as="span"> として解決。

<Link as="span" color="blue.500" textDecoration="underline">
  <NextLink href="/about">...</NextLink>
</Link>
あしやひろあしやひろ

getStaticProps() で外部APIからデータ(microCMSにアップロードした画像)取得してる処理があって、revalidate 設定してるから追加で画像増やしても勝手に新しいデータ取得してくれるでしょとか思ってたけど、例えばスマホでサイト開きっぱなしで気が向いたら閲覧する人の場合、内部リンクは全てnext/linkを使っているので、画面を更新しないと新しいデータが永遠に落ちてこない。そういうときのためにSWRがあるのかぁ。

あしやひろあしやひろ

2020年師走における Next.js をベースとしたフロントエンドの環境構築 - 11. テストの追加 を参考にJestを導入。記事にある、とりあえずテストが動いてるかを確認するテストを実行したところ 'React' refers to a UMD global, but the current file is a module. Consider adding an import instead. というエラーが出た。

これはどうやら import React from "react" を省略する書き方をしてるせいらしい。 > フロントエンドテストのまとめ

jest.config.jsglobals のところを修正して解決

...
  moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"],
  globals: {
    "ts-jest": {
      tsconfig: {
        jsx: "react", // ← ここを "react-jsx" に書き換える
      },
    },
...
あしやひろあしやひろ

アプリも正常に動くしテストも通っているけど、VSCodeでテストファイルで警告が出ていた

"Support for the experimental syntax 'jsx' isn't currently enabled"

検索して一番上の Webpack+BabelでReactの環境構築時に"Support for the experimental syntax 'jsx' isn't currently enabled"が出たときの解消メモ の通り npm install --save-dev @babel/preset-react して .babelrc を以下のように用意

{
  "presets": ["@babel/preset-env","@babel/preset-react"]
}

でも直らないのでエラーメッセージを見直してみると以下のように書いてあった

Add @babel/preset-react (https://git.io/JfeDR) to the 'presets' section of your Babel config to enable transformation.
If you want to leave it as-is, add @babel/plugin-syntax-jsx (https://git.io/vb4yA) to the 'plugins' section to enable parsing.

@babel/plugin-syntax-jsx をインストールして .babelrc を更新

{
  "presets": ["@babel/preset-env", "@babel/preset-react"],
  "plugins": ["@babel/plugin-syntax-jsx"]
}

まだエラーが出るけど、メッセージが変わってて @babel/preset-env が見つからないって出てた。参考にした記事ではインストールしてなかったけど、インストールしないといけないらしい。インストールしたら解決した。

あしやひろあしやひろ

上のBabel関連のやつ、VSCodeのエラーがでなくなってめでたしと思ってたけど、next dev および vercel dev どちらの場合も ReferenceError: regeneratorRuntime is not defined というエラーが出て、開発サーバーが立ち上がらなくなってた。

いろいろ調べた結果、babel関連をアンインストールして .babelrc 削除したら直った。これで上の対応する前の状態に戻ったはずなんだけど、なぜかVSCodeの警告も出ないので一旦これで様子見。上の時点でVSCode再起動したら警告でなくなってた可能性あるかも…

あしやひろあしやひろ

一晩明けてマシン再起動し、上の警告出るかなーとVSCode開いてみたら、警告は出ないけど npm run test がコケるようになってた。

Property ‘toBeTruthy’ does not exist on type ‘Assertion’.ts

Nuxt.jsでjestとcypressでテストしまくる。with Typescript という記事のとおり、tsconfig.json に exclude にcypressを追加したら直った。

ついでに Cypress 関連で他にも色々設定してるので、やる必要があるか調べてみた。

まず、 compilerOptionstypes の部分:

    "types": [
      "@nuxt/types",
      "@types/node",
+     "jest",
+     "cypress"

公式ドキュメント を見たところ、インクルードしたいパッケージを指定したい場合に書くものらしい。デフォルトでは node_modules/@types なものが全てインクルードされるらしいので書かないで良さそう。

また、以下の記述はeslint-plugin-cypress 関連の記述らしい。

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
+   "cypress/globals": true,
  },
  extends: [
    "@nuxtjs/eslint-config-typescript",
    "prettier",
    "prettier/vue",
+   "plugin:cypress/recommended",
    "plugin:prettier/recommended",
    "plugin:nuxt/recommended",
  ],
+ plugins: ["cypress", "prettier"],
  // add your custom rules here
  rules: {},
};

Cypress 導入の方法まとめ

test とはいえコードを綺麗に保ちたかったので、 cypress 用のテストファイルにも eslint, prettier を導入した。

とのことなので入れたほうが良さそう。 npm install eslint-plugin-cypress --save-dev して上記記述を追加。

あしやひろあしやひろ

パフォーマンス計測にlighthouseを導入。Performanceの項目が致命的…本番だと70代後半で、ローカルだと5とかなんだけど???

まあこれは後々対応していくとして、Accessibility の項目で <img> に width/height が無くて怒られてた。Chakra-UIの <Avatar> を使ってたところなんだけど、こいつには width/height は指定できない。 <Image> なら htmlWidth とかあるんだけど、よく考えたらそもそも next/image の <Image> を使いたいので、そっちに CSS Modules でスタイル当てて対応した。

あしやひろあしやひろ

lighthouse の Accessibility、続いては <html> に lang 属性が無くて怒られてた。Next.js公式ドキュメント - Custom Document の例をそのまま持ってきて _document.tsx として保存したら eslintの警告が出た。いろいろ調べつつ以下の形に。

import NextDocument, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
  DocumentProps,
  DocumentInitialProps,
} from "next/document"

class Document extends NextDocument<DocumentProps> {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    const initialProps = await NextDocument.getInitialProps(ctx)
    return { ...initialProps }
  }

  render(): JSX.Element {
    return (
      <Html lang="ja">
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default Document

参考: How to properly type the _document.tsx file from Next.js?

あしやひろあしやひろ

Next.jsで作ったブログのパフォーマンス改善 を参考に Webpack Bundle Analyzer を導入。simple-react-lightboxが巨大過ぎる。VSCodeのimportの行で赤かったもんね…analyzeしなくてもあの表示見ればだいたい分かるのか。

とりあえず Dynamic import を試してみる。

普通に import する代わりに以下のように書き換え。

SimpleReactLightbox は default export されてるので簡単。

_app.tsx
const SimpleReactLightbox = dynamic(() => import("simple-react-lightbox"))

SRLWrapper のほうはこんな感じで import する。

image-gallery.tsx
// @ts-ignore
const SRLWrapper = dynamic(() =>
  import("simple-react-lightbox").then((mod) => mod.SRLWrapper)
)

ここで型で怒られるので ts-ignore する。そしたら ts-ignore 使うなって怒られたので eslintrc に以下のルール追加。

"@typescript-eslint/ban-ts-comment": "off",

ローカルで Performance 5 → 13 に改善されたので、vercelにデプロイして再計測。(ローカルで以上にスコア低いのなんなんだろう…気が向いたら調べる)

スコアちょっと良くなった。赤がなくなったし黄色も減って緑が増えた。

ちなみにLightboxはちゃんと動いてる。

あしやひろあしやひろ

↑はずっと Mobile のほうを計測してたみたい。試しにDesktopのほう計測してみたら、ほぼ💯点だった

具体的に Mobile のほうどうやったら改善するのか今のところ謎。

あしやひろあしやひろ

Next.jsとは関係ないけど、コンテンツ管理をMicroCMSでしてて、ブログ記事とかカテゴリーのリンクのslugをフィールドとして設定してたけど、いざAPIで取得しようとするとMicroCMSではオブジェクト形式のレスポンスを取得する場合はコンテンツIDで取得しようとする。slugでGETなんてことはできない。

どうしようか悩んでたら、そもそもコンテンツIDを編集できるのに気づいた。公式ブログでもslugとして利用されたり…みたいなこと書いてた。なのでMicroCMSを使ってる間は「slug = コンテンツID」という気持ちでいなければならない。