Next.js マークアップ観点の勘所

フロントをNext.js(TypeScript)、ヘッドレスCMSをNewtでサイトを作ってみて、勘所とまでいくか分からないですがつまづいたところ中心のメモです。
型
型の作成
型の作成はNewtのJSONプレビューから quicktype 使ったら早かった。
ここで生成したコードを types/**.ts にコピペして整える。
子要素を渡さないときの型
デフォルトのESLint設定でエラーになるが、 ReactNode を使うようにした。
// components/children.tsx
import { ReactNode } from 'react'
export default function Children({ children }: {children: ReactNode}) {
...
参考: TypeScriptでReactを開発するとき、childrenの型をどうするか考える 👦 - みかづきブログ・カスタム
コンポーネント化
ファイル階層
正解というか一般的なものがつかめてないが、
.
├── components/
│ └── **.tsx
├── pages/
│ └── **.tsx
└── styles/
├── foundation/
│ ├── variables.scss
│ ├── mixin.scss
│ └── global.scss
├── components/
│ └── **.module.scss
└── page/
└── **.module.scss
のような感じで、コンポーネントが多いようならさらに階層切ってあげるとかでとりあえずの管理はできそう。
CSS
リセットCSS(ress)
今の時代は ress があついらしい。
// _app.tsx
import 'ress'
SCSSの有効化
sass をインストール後、
// next.config.js
const path = require('path')
const nextConfig = {
...
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
},
...
}
これで **.modules.scss が使えるようになる。
ファイル名に modules. が付いたSCSSがモジュール用と定義される。
共通SCSS
**.modules.scss 以外のグローバルなSCSSを各PagesやComponentsでインポートしようとすると怒られる。
先の foundation/variables.scss のようにして、 global.scss の中でインポートする。
global.scss は _app.tsx でインポートするとすべての画面で適用される。(html, body への指定など)
// styles/foundation/global.scss
@import './variables.scss';
@import './mixin.scss';
body {
background: $hoge;
@include fuga {
}
}
...
// _app.tsx
import '@/styles/foundation/global.scss'
CSS Modules
importしたSCSSに記述したクラスを参照する。
慣れないとなかなか辛い。
import styles from '@/styles/components/Button.module.scss'
...
return (
// クラス単数(ハイフンなし)の場合
<button className={styles.className}>ラベルテキスト</button>
// クラス単数(ハイフンあり)の場合
<button className={styles['class-name']}>ラベルテキスト</button>
// クラス複数の場合
<button className={`${styles['className01']} ${styles['class-name-02']}`}>ラベルテキスト</button>
)
参考: CSS ModulesでCSSを書く時に実務で必要になる書き方まとめ - Satoshi Murata
CSS変数
よく使うものでもないが、取ってきたデータに色情報があってCSS変数とする場合、 styled-components をインストールして使う。
// Post.tsx
import { createGlobalStyle } from 'styled-components'
export default function Post({ post, posts }: { post: Post, posts: Post[] }) {
const GlobalStyles = createGlobalStyle`
html {
--color-text: ${post.colorText};
}
`
return (
<>
<GlobalStyles />
...
</>
)
}
// Post.module.scss
$color_text: var(--color-text);
.title {
color: $color_text;
}
参考: How to use CSS variables with React
画像
next/image
<Image> コンポーネントで読み込むといいことがたくさんあるらしい。
import Image from 'next/image'
...
<Image
src={post.image.src}
width={post.image.width}
height={post.image.height}
alt={post.title}
quality={70}
priority={true}
unoptimized
/>
最後の unoptimized をしないと逆に表示がもたついた。
じゃあ意味あるの、って感じだが外部でホスティングしている画像だからかもしれない。
参考: next/image | Next.js
参考: 【Next.js和訳】API Reference/ next/image
外部でホスティングしている画像を読み込みする際は設定にドメインの追加が必要。
// next.config.js
const nextConfig = {
...
images: { // next/image で外部画像を参照
domains: ['storage.googleapis.com'],
},
...
}
JSX
ifの分岐
export default function Posts({ posts }: { posts: Post[] }) {
let detectStatus = (post: Post) => {
if (post.isPublish) {
return (
<p>公開中の投稿</p>
...
)
} else {
return (
<p>非公開の投稿</p>
...
)
}
}
return (
<>
<ul>
{posts.map((post) => {
return (
<li key={post._id}>
{detectStatus(post)}
</li>
)
})}
</ul>
</>
)
}
参考: React JSX の中で if で分岐させたい - かもメモ
テキストの改行
いろいろやり方あるようだが react-nl2br を使うと簡単。
import nl2br from 'react-nl2br'
...
<p>{nl2br(post.description)}</p>
その他
定数
これもいろいろやり方あるようだが next.config.js に作ってみた。
// next.config.js
const nextConfig = {
...
env: { // 定数
customKey: 'my-value',
},
...
}
<p>{process.env.customKey}</p>
参考: next.config.js: Environment Variables | Next.js
urlの末尾にスラッシュつけたい
デフォルトで [id].tsx とか動的にするとスラッシュがつかない。
これも next.config.js で設定。
// next.config.js
const nextConfig = {
...
trailingSlash: true, // URL末尾にスラッシュを追加
...
}
参考: next.config.js: 末尾のスラッシュ | Next.js
meta設定
定数作ってちまちま pages/**.tsx のHeadに書いていたが、 この方法 の方ががよさそう。
画面遷移時のローディング
NProgress はちゃちゃっと入れれる。
TypeScriptの場合は types/nprogress もインストールする。
// _app.tx
import Router from 'next/router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
Router.events.on('routeChangeStart', () => NProgress.start())
Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done())
Vercelのドメイン設定
CNAMEは cname.vercel-dns.com. を登録する。
参考: Vercelでデプロイしたサイトに独自ドメインのサブドメインを設定する | okaryo.log
ひとこと
最低限マークアップするくらいだと抵抗は少なくなってきました。
値の受け渡しは雛形通りだったりストア周り触れてなかったりするので触れていきたいです。