Closed15
Next.jsのdevelopment modeとproduction modeで画面遷移の挙動がちがう問題を解決したい
おきたこと
useRouterのpushを使ったpage間の遷移が
Next.jsのdevelopment modeでは想定した挙動をするが
production mode(next buildしてからnext startする)の場合に画面遷移がとても遅く1回しか遷移できない。
またheadless UIなどのコンポーネントが描画されない。
環境
package.json
package.json
{
"dependencies": {
"@headlessui/react": "1.3.0",
"@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.6.0",
"axios": "0.21.1",
"classnames": "2.3.1",
"moment": "2.29.1",
"next": "10.2.3",
"next-seo": "4.26.0",
"react": "17.0.2",
"react-dates": "21.8.0",
"react-dom": "17.0.2",
"react-hook-form": "7.9.0",
"react-icons": "4.2.0",
"react-paginate": "7.1.3",
"react-select": "4.3.1",
"react-textarea-autosize": "8.3.3",
"react-toastify": "7.0.4",
"react-tooltip": "4.2.21",
"yup": "0.32.9"
},
"devDependencies": {
"@openapitools/openapi-generator-cli": "2.3.5",
"@slack/web-api": "6.2.4",
"@stoplight/prism-cli": "4.2.6",
"@storybook/addon-essentials": "6.3.0",
"@storybook/addon-links": "6.3.0",
"@storybook/addon-postcss": "2.0.0",
"@storybook/addon-storyshots": "6.3.0",
"@storybook/react": "6.3.0",
"@types/classnames": "2.3.1",
"@types/jest": "26.0.23",
"@types/node": "15.0.2",
"@types/react-dates": "21.8.2",
"@types/react-dom": "17.0.8",
"@types/react-paginate": "7.1.0",
"@types/react-select": "4.0.16",
"@types/react-tooltip": "4.2.4",
"@types/tailwindcss": "2.2.0",
"@types/yup": "0.29.11",
"@typescript-eslint/eslint-plugin": "4.28.0",
"@typescript-eslint/experimental-utils": "4.28.0",
"@typescript-eslint/parser": "4.28.0",
"autoprefixer": "10.2.6",
"babel-jest": "26.6.3",
"cypress": "7.6.0",
"env-cmd": "10.1.0",
"eslint": "7.29.0",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-fp": "2.3.0",
"eslint-plugin-import": "2.23.4",
"eslint-plugin-jest": "24.3.6",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-prettier": "3.4.0",
"eslint-plugin-react": "7.24.0",
"eslint-plugin-rulesdir": "0.2.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"husky": "6.0.0",
"jest": "26.6.3",
"jest-watch-typeahead": "0.6.4",
"lint-staged": "11.0.0",
"postcss": "8.3.5",
"postcss-nested": "5.0.5",
"prettier": "2.3.2",
"redoc": "2.0.0-rc.54",
"reg-keygen-git-hash-plugin": "0.10.16",
"reg-notify-github-plugin": "0.10.16",
"reg-publish-s3-plugin": "0.10.16",
"reg-suit": "0.10.16",
"storycap": "3.0.4",
"tailwindcss": "2.2.4",
"ts-node": "9.1.1",
"typescript": "4.3.2"
},
}
正しく動いているNext.jsの別プロジェクトと比較
両方のプロジェクトでnext buildしてからnext startで確認
普通に画面遷移するプロジェクト
遷移前
遷移後
問題が起きてるプロジェクト
遷移前
useRouterのpush実行時
少し時間が経ってから画面遷移された後
やったこと
tailwindcssのjitモードを使わないようにした
- 頻繁にビルド時に
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
のエラーが出るようになった -
w-[300px]
みたいな書き方をやめた(jitモード使えなくなるし必然ではある)
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
の対応はリンクメモするの忘れたけど
-
madge
をインストールし、yarn madge --circular src --extensions ts,tsx
で循環参照がないか確認→なかった -
NODE_OPTIONS='--max-old-space-size=8086' env-cmd -e mock next build && next export
ビルド時のnode.jsのオプション追加
jitモードを使わないようにするとtableコンポーネントを使ったページ以外は正常に動くようになった
tableコンポーネントの実装
export type TableRow = {
id: string
error?: boolean
data: {
[fieldName: string]: ReactNode
}
}
export type TableColumn = {
label: string
fieldName: string
}
export type TableProps = {
/**
* テーブルの行に入る値
*/
rows: TableRow[]
/**
* テーブルの列に入る値
*/
columns: TableColumn[]
}
// HACK: ループ回数多い気がする
// パフォーマンス悪かったらリファクタする
const renderBody = (row: TableRow, columns: TableColumn[]): JSX.Element => {
const filterColumns = columns.filter((column) => column.fieldName in row.data)
return (
<tr
key={row.id}
className={classnames(styles.row, { [styles.error]: row.error })}
>
{filterColumns.map(({ fieldName }) => (
<td key={`${fieldName}-${row.id}`} className={styles.column}>
{row.data[fieldName]}
</td>
))}
</tr>
)
}
export const Table: FC<TableProps> = ({ rows, columns }: TableProps) => {
return (
<table className={styles.table}>
<thead className={styles.header}>
<tr>
{columns.map(({ fieldName, label }) => (
<th key={fieldName}>
<Txt fontSize="text-xs" bold>
{label}
</Txt>
</th>
))}
</tr>
</thead>
<tbody className={styles.body}>
{rows.map((r) => renderBody(r, columns))}
</tbody>
</table>
)
}
セルの中身をReactNode
で受け取ってるのが悪い?
tableコンポーネントの何が悪いのか問題を切り分ける
- セルの中身をコンポーネントではなくstringを渡せば大丈夫?→ダメ
- 表示部分の条件分岐が関係してる(propsによって表示される要素が変わる)?→ダメ
- presenterとcontainerでコンポーネント分けてるのが悪い?→ダメ
- 同一ファイルにpresenterとcontainer両方のコンポーネントがあるのが悪い?→正しく動いた
- tableコンポーネントだけを呼ぶようにして、presenterを削除した
- containerでpresenterの実装をcontainerに持ってくる→ダメ
presenterの実装に問題がある?
presensterの実装
<section className={styles.wrap}>
<div className={styles.top}>
<form
className={classnames(styles['search-wrap'], {
[styles['show-checkbox']]: showDeletedCheckBox,
})}
>
{showDeletedCheckBox && (
<div className="flex items-center">
<CheckboxField
items={[
{
name: 'showDeleted',
label: <Txt fontSize="text-xs">解約した店舗も表示する</Txt>,
register: register('showDeleted'),
},
]}
/>
</div>
)}
<div className={styles.search}>
<SearchField
register={register('searchValue')}
onKeyPressEnter={handleSearch}
/>
</div>
</form>
<Table rows={rows} columns={columns} />
</div>
<div className={styles.footer}>
<Pagination {...paginationProps} />
</div>
</section>
tableコンポーネントのpresenterの何が悪いのか
- 表示の条件分岐が悪い?→ダメ
- tableコンポーネント以外のコンポーネントが悪い?→ダメ
- tableコンポーネントと素のタグ以外使わないようにしたけどダメだった
- classnamesで条件分岐でclassNameを渡してるのが悪い?→動いた
- もしやclassnamesの書き方がes6の書き方でやってるのにes5指定してた?
- classnamesの条件分岐を元に戻してtsconfigのcompilerOptionsのtargetをes6にしてみる→動いた
- table以外のコンポーネントを元に戻してみる→ダメ
- search field コンポーネントをコメントアウト→ダメ
- checkbox field コンポーネントをコメントアウト→ダメ
- pagination コンポーネントをコメントアウト→ダメ
-
import classnames from 'classnames'
が悪い? - pagination, search field, checkbox fieldを取り除いたら動いた
- pagination追加しても動いた
- search field追加しても動かない
- checkbox field追加しても動かない
tableコンポーネント以外のコンポーネントが悪い
<section className={styles.wrap}>
<div className={styles.top}>
<form
className={styles['search-wrap']}
>
<div className={styles.search}>
</div>
</form>
<Table rows={rows} columns={columns} />
</div>
</section>
classnamesで条件分岐
<form
className={classnames(styles['search-wrap'], {
[styles['show-checkbox']]: showDeletedCheckBox,
})}
>
classnames、css modulesの場合はbindを使う書き方が推奨されてる?
search fieldの何が悪いのか
- classnames?→ダメ
- search fieldコンポーネントの代わりにtext fieldコンポーネント使ったら動いた
search fieldコンポーネント
import classnames from 'classnames'
import { FC } from 'react'
import { UseFormRegisterReturn } from 'react-hook-form'
import { Icon } from 'components/atoms/Icon'
import styles from './styles.module.css'
export type SearchFieldProps = {
/**
* react-hook-form のregister('fieldName')の返り値の型
*/
register: UseFormRegisterReturn
/**
* errorかどうか
*/
error?: boolean
/**
* プレースホルダ
*/
placeholder?: string
/**
* 初期値
*/
defaultValue?: string
/**
* enterキーをpressした時のイベントハンドリング
*/
onKeyPressEnter: () => void
}
export const SearchField: FC<SearchFieldProps> = ({
register,
error,
placeholder,
defaultValue,
onKeyPressEnter,
}: SearchFieldProps) => {
return (
<div className={styles.wrap}>
<input
{...register}
className={classnames(styles.input, {
[styles.error]: error,
})}
type="text"
placeholder={placeholder}
defaultValue={defaultValue}
onKeyPress={(e) => {
if (e.key !== 'Enter') return
e.preventDefault()
onKeyPressEnter()
}}
/>
<span className={styles.icon}>
<Icon name="search" color="text-textLight" fontSize="text-lg" />
</span>
</div>
)
}
text fieldコンポーネント
//import classnames from 'classnames'
import { FC } from 'react'
import { InputText, InputTextProps } from 'components/atoms/forms/InputText'
import { Txt } from 'components/atoms/texts/Txt'
import { LabelBadge } from 'components/molecules/badges/LabelBadge'
import styles from './styles.module.css'
export type TextFieldProps = {
/**
* input textのprops
*/
inputTextProps: InputTextProps
/**
* label
*/
label: string
/**
* ラベルに米印をつけるかどうか
*/
optioned?: boolean
/**
* 最大の文字数
*/
maxCount?: number
/**
* 現在の文字数
*/
currentCount?: number
/**
* 必須かどうか
*/
required?: boolean
}
export const TextField: FC<TextFieldProps> = ({
inputTextProps,
label,
optioned,
currentCount,
maxCount,
required,
}: TextFieldProps) => {
return (
<div className={styles.wrap}>
<div className={styles['label-wrap']}>
<Txt fontSize="text-sm">{label}</Txt>
<LabelBadge required={required} />
{optioned && (
<Txt color="text-alert" fontSize="text-sm">
※
</Txt>
)}
</div>
<InputText {...inputTextProps} />
{currentCount !== undefined && maxCount && (
<div className={styles['count-wrap']}>
<Txt fontSize="text-xs">
{currentCount} / {maxCount}
</Txt>
</div>
)}
</div>
)
}
react-tooltipの問題はこれ?
一縷の望みをかけてnext.jsのバージョンを11にあげたら動いた笑
きっかけはこのissue
Next.jsがどのようにビルドしてるのか追わんと同じような地雷踏みそう
関連する修正はここら辺っぽい
やったこと
yarn add next@latest
-
yarn add --dev eslint-config-next
してnextのversionを11.0.1
にあげた
このスクラップは2021/07/05にクローズされました