Next.js 14・TailwindCSSでのフロントエンドセットアップ手順
前提
フォントや色味など、どういう手順でセットアップしてから開発に進むとスムーズか、検証を兼ねた備忘録を作成しました。
(Next.js・TailwindCSS・Storybookの導入までは終わっている前提です。)
Next.js 14でのFont最適化の話が多めとなります。
実作業内で見直しがあれば随時追記していきます。
環境
Next.js 14(App router)・Typescript 5・TailwindCSS 3・Storybook 7などを使用しています。
StorybookでTailwindCSSが動くようにする
(23/12/25追記)若干導入の話寄りですが、やり忘れていたので書きます。
基本的な設定はとても楽になっており、以下のコマンドを実行するだけです。
npm install --save-dev @storybook/addon-styling
node node_modules/@storybook/addon-styling/bin/postinstall.js
yarnでの方法はこちらにまとめられています🙏
@のパスエイリアスが通るようにする
Next.jsによりimport
の際には@
から始まるモジュールパスエイリアスが書かれると思いますが、Storybook上では、以下のようなエラーが起きてしまいます。
ERROR in ./src/app/ui/footer/footer.tsx 4:0-50
Module not found: Error: Can't resolve '@/utils/styleUtils' in '/(省略)/src/app/ui/footer'
なのでパスエイリアスが通るように.storybook/main.ts
を修正します。
公式👆の
However, if you're working with a framework that provides a default aliasing configuration (e.g., Next.js, Nuxt)
以下を参照して、次の設定を.storybook/main.ts
に書き足します。
import type { StorybookConfig } from '@storybook/nextjs'
+ const path = require('path')
const config: StorybookConfig = {
// ...
+ webpackFinal: async (config: any) => {
+ // Add path aliases
+ config.resolve.alias['@'] = path.resolve(__dirname, '../src')
+ return config
+ },
// ...
}
// ...
tailwind.config.tsを書く
既にFigmaやXdなどでカラースタイルが作成されている前提です。
色味の設定が一番頭を使わずにできるので、まずはtailwind.config.ts
のextend
に書いていきます。
SNSのブランドカラーなどもまとめて書いておくと使い回しが効いていいかもしれません。
colors: {
gray: {
200: '#E8E8E8',
},
twitter: '#000000',
line: '#06C755',
},
fontFamily
も設定することになりますが、一旦後回しにします。
よく使う幅や高さがあれば、これもspacing
に書いておくとよさそうです。
spacing: {
'1px': '1px',
'2px': '2px',
'3px': '3px',
'10px': '10px',
'20px': '20px',
'30px': '30px',
'60px': '60px',
},
Next.jsでのフォント最適化
next/fontを使う場合
公式を参考に、Next.jsでの設定を行います。./src/app/ui/fonts.ts
を作成してapp/layout.tsx
に適用する方法が書かれていますが、今回はtailwindCSS向けの設定方法が書かれていたので、そちらの方法で行います。
この場合fonts.ts
は不要となり、./src/app/layout.tsx
にnext/font
をimport
します。
序盤で説明されている方法と異なり、定義にvariable
を追加してhtml
タグにて呼びます。
+ import { Noto_Sans_JP, Audiowide } from 'next/font/google'
+ const audiowide = Audiowide({
+ display: 'swap',
+ subsets: ['latin'],
+ weight: '400',
+ variable: '--font-audiowide',
+ })
+ const noto = Noto_Sans_JP({
+ display: 'swap',
+ preload: false,
+ variable: '--font-noto',
+ })
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
+ <html lang='ja' className={`${noto.variable} ${audiowide.variable}`}>
<body>
{children}
</body>
</html>
)
}
ちなみに、Noto Sans JPは日本語フォントのためsubsets
は不要となり、代わりにpreload: false
とすることで調整しました。またNoto Sans JPはVariableフォントのため、weight
の設定は行いませんでした。
(こちらの記事を参考にしました!🙏)
tailwind.config.tsにfontFamily部分を追記する
fontFamily: {
noto: ['var(--font-noto)', 'sans-serif'],
audiowide: ['var(--font-audiowide)', 'var(--font-noto)', 'sans-serif'],
},
これで、font-noto、font-audiowide
というclassName
が使用できるようになりました。
StorybookにFontを適用する
(23/12/26 追記)先ほど作成したVariableがStorybook上で動作するように.storybook/preview.tsx
を編集する必要があります。
(divタグなどを使用するため、preview.ts
をpreview.tsx
に変更が必要です)
+ import React from 'react'
import type { Preview } from '@storybook/react'
import { withThemeByClassName } from '@storybook/addon-styling'
+ import { Noto_Sans_JP, Audiowide } from 'next/font/google'
import '../src/app/globals.css'
+ const audiowide = Audiowide({
+ display: 'swap',
+ subsets: ['latin'],
+ weight: '400',
+ variable: '--font-audiowide',
+ })
+ const noto = Noto_Sans_JP({
+ display: 'swap',
+ preload: false,
+ variable: '--font-noto',
+ })
const preview: Preview = {
decorators: [
(Story) => (
+ <div className={`${`font-sans ${audiowide.variable} ${noto.variable}`}`}>
<Story />
</div>
),
],
}
export default preview
これで、開発環境とStorybook上のどちらでも同じフォントが使用できる状態となります。
next/fontを使わない(Linkタグを使用する)場合
ここまではnext/fontを使用した設定方法でした。しかし、 next/font/googleにはMaterial symbolsが含まれていない(Googleが提供してるのに!) ため、別の手法で読み込む必要があります。
この件については、こちらでディスカッションが行われています:https://github.com/vercel/next.js/discussions/42881
結論から言えば、./src/app/layout.tsx
に直接linkタグを書くことができますが、いくつか注意事項があるのでまとめました。
最新のNext.jsではこれまでhead
内に書いていた様々なメタデータをNext.jsが用意するmetadata APIを使って、効率よく書くことが可能です。
これには、meta
タグおよびlink
タグが含まれていますが、すべてのlink
タグが含まれているわけではありません。そのため、link
タグのrel
がどの種類かによって対応が変わります。
1. Link relが"stylesheet"の場合
./src/app/layout.tsx
に以下のように追記するだけで大丈夫です。
その時に、ESlintが警告を出すことがありますが、最新のNext.jsに対応した内容ではない(app routerではなくpages routerにおける対処法となっている)ため、無視してよいでしょう。
<html lang='ja' className={`${noto.variable} ${audiowide.variable}`}>
+ <head> // headを追加する
+ {/* eslint-disable-next-line @next/next/no-page-custom-font */}
+ <link
+ rel='stylesheet'
+ href='https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&display=swap'
+ />
+ </head>
<body>
{children}
</body>
</html>
2. Link relがpreload, preconnect, dns-prefetchの場合
metadata
ではサポート外となりますが、ReactDOM
のMethodを使うことが可能です。
具体的には、
にあるように、./src/app/preload-resources.tsx
を作成し、以下のように書きます。
'use client'
import ReactDOM from 'react-dom'
export function PreloadResources() {
ReactDOM.preload('...', { as: '...' }) // 使用しなかったので詳細省きます
ReactDOM.preconnect('https://fonts.googleapis.com')
ReactDOM.preconnect('https://fonts.gstatic.com', { crossOrigin: 'anonymous' })
ReactDOM.prefetchDNS('...') // 使用しなかったので詳細省きます
return null
}
preconnect
に書いた内容は、
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
を置き換えたものとなります。crossorigin
は属性値を指定しない場合はanonymous
として扱われているとのことだったので、置き換えました。
作成したpreload-resources.tsx
を./src/app/layout.tsx
で読み込みます。
+ import { PreloadResources } from './preload-resources.tsx'
// ...
<html lang='ja' className={`${noto.variable} ${audiowide.variable}`}>
<head>
+ <PreloadResources />
// ...
これで完了です。
StorybookにFontを適用する
next/fontを使わない場合、.storybook/preview-head.html
を作成して、そちらに先ほど使用したのと同じ<Link />
を貼る必要があります。
こちらの記事を参考にしました🙏
おまけ:material-symbols-outlinedクラスを作成する
このままだと使いにくいので、ついでに./src/app/globals.css
にクラスを作成します。
/* fallback */
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 400;
src: url(/material.woff2) format('woff2');
}
.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
input::-webkit-input-placeholder {
font-family: 'Material Symbols Outlined';
font-size: 24px;
position: absolute;
bottom: 7px;
}
input::-moz-placeholder {
font-family: 'Material Symbols Outlined';
font-size: 24px;
position: absolute;
bottom: 7px;
}
input:-ms-input-placeholder {
font-family: 'Material Symbols Outlined';
font-size: 24px;
position: absolute;
bottom: 7px;
}
input:-moz-placeholder {
font-family: 'Material Symbols Outlined';
font-size: 24px;
position: absolute;
bottom: 7px;
}
これらを書いておくと、className
として呼び出す・input
要素のplaceholder
にMaterial Symbolsの適用ができるようになります。
styleUtils.tsxを書く
今の時点で頻出しそうなclassName
の組み合わせがあればまとめておきます。
と言いつつ、書いていく中でまとめたいものが増えていくのがほとんどだと思うので、思いつかなければ思いつかなければファイルだけ作っておくのでも十分だと思います。
今回は./src/app/ui
に配置しました。
//ここはお好みでどうぞ
export const heading1Style = 'font-noto text-2xl'
普段は文字のサイズや色、HoverやFocusで使うスタイルをまとめています。
終わりに
一通り、自分がプロジェクトでやる方法をまとめてみました。
Next.jsのApp routerに関する情報自体もまだこれから増えていくところだと思うので、何かお役に立てば幸いです。
実際やっていく中で、作業の順番が前後することが度々あり、本記事の構成も一部公開後に修正を加えました。
セッティングは疎かにできない一方なるべく早く終わらせたい作業だと思うので、今後も見直しがあれば更新予定です。より効率良い手法について、コメントお待ちしております!
Discussion