Next.jsでのグローバルスタイリングとローカルスタイリング
公開: 2022-03-10Next.jsを使用して複雑なフロントエンドプロジェクトを管理する素晴らしい経験をしました。 Next.jsは、JavaScriptコードを整理する方法について意見がありますが、CSSを整理する方法についての意見は組み込まれていません。
フレームワーク内で作業した後、Next.jsの指針となる哲学に準拠し、CSSのベストプラクティスを実行していると私が信じる一連の組織パターンを見つけました。 この記事では、これらのパターンを示すために、一緒にWebサイト(喫茶店!)を構築します。
注: Reactの基本を理解し、いくつかの新しいCSSテクニックを習得することは良いことですが、Next.jsの経験はおそらく必要ありません。
「昔ながらの」CSSを書く
Next.jsを最初に調べるとき、ある種のCSS-in-JSライブラリの使用を検討したくなるかもしれません。 プロジェクトによってはメリットがあるかもしれませんが、CSS-in-JSでは多くの技術的な考慮事項が導入されています。 新しい外部ライブラリを使用する必要があり、バンドルサイズが増加します。 CSS-in-JSは、グローバル状態に追加のレンダリングと依存関係を引き起こすことによって、パフォーマンスに影響を与える可能性もあります。
推奨読書: Aggelos Arvanitakisによる「Reactアプリにおける最新のCSS-in-JSライブラリの目に見えないパフォーマンスコスト)」
さらに、Next.jsのようなライブラリを使用することの全体的なポイントは、可能な限りアセットを静的にレンダリングすることです。したがって、CSSを生成するためにブラウザーで実行する必要があるJSを作成することはあまり意味がありません。
Next.js内でスタイルを整理するときに考慮しなければならない質問がいくつかあります。
フレームワークの規則/ベストプラクティスにどのように適合できますか?
「グローバル」なスタイルの問題(フォント、色、メインレイアウトなど)と「ローカル」の問題(個々のコンポーネントに関するスタイル)のバランスをどのように取ることができますか?
私が最初の質問に対して思いついた答えは、古き良きCSSを単純に書くことです。 Next.jsは、追加のセットアップなしでこれをサポートするだけではありません。 また、パフォーマンスが高く静的な結果が得られます。
2番目の問題を解決するために、私は4つの部分に要約できるアプローチを取ります。
- デザイントークン
- グローバルスタイル
- ユーティリティクラス
- コンポーネントスタイル
ここでは、アンディ・ベルのCUBE CSS (「構成、ユーティリティ、ブロック、例外」)のアイデアに感謝しています。 この組織の原則について聞いたことがない場合は、SmashingPodcastの公式サイトまたは機能を確認することをお勧めします。 CUBE CSSから採用する原則の1つは、CSSカスケードを恐れるのではなく受け入れるべきであるという考えです。 これらのテクニックをウェブサイトプロジェクトに適用して学びましょう。
入門
お茶が美味しいので、お茶屋さんを建てます。 まず、 yarn create next-app
を実行して、新しいNext.jsプロジェクトを作成します。 次に、 styles/ directory
内のすべてを削除します(これはすべてサンプルコードです)。
注:完成したプロジェクトをフォローしたい場合は、こちらで確認できます。
デザイントークン
ほとんどすべてのCSS設定では、グローバルに共有されるすべての値を変数に格納することには明らかな利点があります。 クライアントが色の変更を要求した場合、変更の実装は、大規模な検索と置換の混乱ではなく、ワンライナーです。 したがって、Next.js CSSセットアップの重要な部分は、サイト全体のすべての値をデザイントークンとして保存することです。
これらのトークンを保存するには、組み込みのCSSカスタムプロパティを使用します。 (この構文に慣れていない場合は、「CSSカスタムプロパティの戦略ガイド」を確認してください。)(一部のプロジェクトでは)この目的でSASS/SCSS変数を使用することを選択したことを述べておく必要があります。 本当の利点は見つからなかったので、通常、他のSASS機能(ミックスイン、イテレーション、ファイルのインポートなど)が必要な場合にのみ、プロジェクトにSASSを含めます。 対照的に、CSSカスタムプロパティはカスケードでも機能し、静的にコンパイルするのではなく、時間の経過とともに変更できます。 それで、今日は、プレーンなCSSに固執しましょう。
styles/
ディレクトリに、新しいdesign_tokens.cssファイルを作成しましょう。
:root { --green: #3FE79E; --dark: #0F0235; --off-white: #F5F5F3; --space-sm: 0.5rem; --space-md: 1rem; --space-lg: 1.5rem; --font-size-sm: 0.5rem; --font-size-md: 1rem; --font-size-lg: 2rem; }
もちろん、このリストは時間の経過とともに増える可能性があります。 このファイルを追加したら、すべてのページのメインレイアウトであるpages / _app.jsxファイルに移動して、次を追加する必要があります。
import '../styles/design_tokens.css'
デザイントークンは、プロジェクト全体の一貫性を維持する接着剤と考えるのが好きです。 これらの変数をグローバルスケールで、また個々のコンポーネント内で参照して、統一された設計言語を確保します。
グローバルスタイル
次は、私たちのウェブサイトにページを追加しましょう! pages / index.jsxファイル(これは私たちのホームページです)に飛び乗ってみましょう。 すべての定型文を削除し、次のようなものを追加します。
export default function Home() { return <main> <h1>Soothing Teas</h1> <p>Welcome to our wonderful tea shop.</p> <p>We have been open since 1987 and serve customers with hand-picked oolong teas.</p> </main> }
残念ながら、見た目は非常にわかりやすいので、基本的な要素( <h1>
タグなど)にいくつかのグローバルスタイルを設定しましょう。 (私はこれらのスタイルを「合理的なグローバルデフォルト」と考えるのが好きです。)特定の場合にそれらをオーバーライドすることがありますが、そうでない場合に何が必要になるかについては適切な推測です。
これをstyles/globals.cssファイル(デフォルトではNext.jsから提供されます)に入れます。
*, *::before, *::after { box-sizing: border-box; } body { color: var(--off-white); background-color: var(--dark); } h1 { color: var(--green); font-size: var(--font-size-lg); } p { font-size: var(--font-size-md); } p, article, section { line-height: 1.5; } :focus { outline: 0.15rem dashed var(--off-white); outline-offset: 0.25rem; } main:focus { outline: none; } img { max-width: 100%; }
もちろん、このバージョンはかなり基本的なものですが、私のglobals.cssファイルは通常、実際には大きくしすぎる必要はありません。 ここでは、基本的なHTML要素(見出し、本文、リンクなど)のスタイルを設定します。 これらの要素をReactコンポーネントでラップしたり、基本的なスタイルを提供するためだけにクラスを常に追加したりする必要はありません。
デフォルトのブラウザスタイルのリセットも含めます。 たとえば、「スティッキーフッター」を提供するために、サイト全体のレイアウトスタイルを使用することもありますが、すべてのページが同じレイアウトを共有している場合にのみ、ここに属します。 それ以外の場合は、個々のコンポーネント内でスコープを設定する必要があります。
フォーカスされたときにキーボードユーザーのインタラクティブな要素を明確に示すために、常に何らかの:focus
スタイリングを含めています。 それをサイトのデザインDNAの不可欠な部分にするのが最善です!
今、私たちのウェブサイトは形になり始めています:
ユーティリティクラス
私たちのホームページが確かに改善できる領域の1つは、テキストが現在常に画面の横に伸びていることです。そのため、幅を制限しましょう。 このページではこのレイアウトが必要ですが、他のページでも必要になるかもしれないと思います。 これは、ユーティリティクラスの優れたユースケースです。
私は、CSSを書く代わりとしてではなく、ユーティリティクラスを控えめに使用しようとしています。 プロジェクトに追加することが理にかなっている場合の私の個人的な基準は次のとおりです。
- 繰り返し必要です。
- それは1つのことをうまく行います。
- これは、さまざまなコンポーネントまたはページに適用されます。
このケースは3つの基準すべてを満たしていると思うので、新しいCSSファイルstyles/utilities.cssを作成して次を追加しましょう。
.lockup { max-width: 90ch; margin: 0 auto; }
次に、import '../styles/utilities.css'
をpages/_app.jsxに追加しましょう。 最後に、pages/index.jsxの<main>
タグを<main className="lockup">
に変更しましょう。
今、私たちのページはさらにまとまっています。 max-width
プロパティを使用したため、レイアウトをモバイルレスポンシブにするためにメディアクエリは必要ありません。 また、 ch
測定単位(約1文字の幅に相当)を使用したため、サイズはユーザーのブラウザのフォントサイズに応じて動的になります。
私たちのウェブサイトが成長するにつれて、私たちはさらにユーティリティクラスを追加し続けることができます。 私はここでかなり実用的なアプローチを取ります:私が働いていて、色か何かのために別のクラスが必要だとわかった場合、私はそれを追加します。 考えられるすべてのクラスを太陽の下で追加するわけではありません。CSSファイルのサイズが大きくなり、コードが混乱する可能性があります。 大規模なプロジェクトでは、いくつかの異なるファイルを含むstyles/utilities/
ディレクトリに分割するのが好きな場合があります。 それはプロジェクトのニーズ次第です。
ユーティリティクラスは、グローバルに共有される一般的な繰り返しスタイリングコマンドのツールキットと考えることができます。 これらは、異なるコンポーネント間で同じCSSを絶えず書き換えることを防ぐのに役立ちます。
コンポーネントスタイル
ホームページは今のところ完成していますが、ウェブサイトの一部であるオンラインストアを構築する必要があります。 ここでの目標は、販売したいすべてのお茶のカードグリッドを表示することです。そのため、サイトにいくつかのコンポーネントを追加する必要があります。
まず、 pages/shop.jsxに新しいページを追加してみましょう。
export default function Shop() { return <main> <div className="lockup"> <h1>Shop Our Teas</h1> </div> </main> }
次に、表示するお茶が必要になります。 各お茶の名前、説明、画像(public /ディレクトリ)を含めます。
const teas = [ { name: "Oolong", description: "A partially fermented tea.", image: "/oolong.jpg" }, // ... ]
注:これはデータフェッチに関する記事ではないため、簡単なルートを使用して、ファイルの先頭に配列を定義しました。
次に、お茶を表示するコンポーネントを定義する必要があります。 components/
ディレクトリを作成することから始めましょう(Next.jsはデフォルトではこれを作成しません)。 次に、 components/TeaList
ディレクトリを追加しましょう。 最終的に複数のファイルが必要になるコンポーネントの場合、通常、関連するすべてのファイルをフォルダー内に配置します。 そうすることで、 components/
フォルダーがナビゲートできなくなるのを防ぎます。
それでは、 components / TeaList/TeaList.jsxファイルを追加しましょう。
import TeaListItem from './TeaListItem' const TeaList = (props) => { const { teas } = props return <ul role="list"> {teas.map(tea => <TeaListItem tea={tea} key={tea.name} />)} </ul> } export default TeaList
このコンポーネントの目的は、お茶を繰り返し処理し、それぞれのリストアイテムを表示することです。次に、 components / TeaList/TeaListItem.jsxコンポーネントを定義しましょう。
import Image from 'next/image' const TeaListItem = (props) => { const { tea } = props return <li> <div> <Image src={tea.image} alt="" objectFit="cover" objectPosition="center" layout="fill" /> </div> <div> <h2>{tea.name}</h2> <p>{tea.description}</p> </div> </li> } export default TeaListItem
Next.jsの組み込みの画像コンポーネントを使用していることに注意してください。 この場合、画像は純粋に装飾的であるため、 alt
属性を空の文字列に設定しました。 ここでは、長い画像の説明でスクリーンリーダーのユーザーを困惑させないようにします。
最後に、 components / TeaList / index.jsファイルを作成して、コンポーネントを外部から簡単にインポートできるようにします。
import TeaList from './TeaList' import TeaListItem from './TeaListItem' export { TeaListItem } export default TeaList
次に、.. / components/TeaListからの../components/TeaList
と<TeaList teas={teas} />
要素をショップページに追加して、すべてをプラグインしましょう。 これで、お茶がリストに表示されますが、それほどきれいではありません。
CSSモジュールを介したコンポーネントとスタイルのコロケーション
カード( TeaListLitem
コンポーネント)のスタイルを設定することから始めましょう。 さて、私たちのプロジェクトで初めて、1つのコンポーネントだけに固有のスタイルを追加したいと思います。 新しいファイルcomponents/TeaList/TeaListItem.module.cssを作成しましょう。
あなたはファイル拡張子のモジュールについて疑問に思うかもしれません。 これはCSSモジュールです。 Next.jsはCSSモジュールをサポートしており、それらに関するいくつかの優れたドキュメントが含まれています。 .TeaListItem
などのCSSモジュールからクラス名を記述すると、自動的に。のようなものに変換されます. TeaListItem_TeaListItem__TFOk_
. TeaListItem_TeaListItem__TFOk_
には、追加の文字がたくさん追加されています。 したがって、サイト内の他の場所で他のクラス名と競合することを心配することなく、任意のクラス名を使用できます。
CSSモジュールのもう1つの利点は、パフォーマンスです。 Next.jsには、動的インポート機能が含まれています。 next / dynamicを使用すると、コンポーネントを遅延ロードして、バンドルサイズ全体に追加するのではなく、必要な場合にのみコードがロードされるようにすることができます。 必要なローカルスタイルを個々のコンポーネントにインポートすると、ユーザーは動的にインポートされたコンポーネントのCSSを遅延ロードすることもできます。 大規模なプロジェクトの場合、コードの重要なチャンクを遅延ロードし、最も必要なJS/CSSのみを事前にロードすることを選択できます。 その結果、私は通常、ローカルスタイルを必要とするすべての新しいコンポーネントに対して新しいCSSモジュールファイルを作成することになります。
ファイルにいくつかの初期スタイルを追加することから始めましょう。
.TeaListItem { display: flex; flex-direction: column; gap: var(--space-sm); background-color: var(--color, var(--off-white)); color: var(--dark); border-radius: 3px; box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); }
次に、 TeaListitem
コンポーネントの./TeaListItem.module.css
からスタイルをインポートできます。 style変数はJavaScriptオブジェクトのように提供されるため、このクラスのようなstyle.TeaListItem.
注:クラス名は大文字にする必要はありません。 モジュール内の大文字のクラス名(および外部の小文字のクラス名)の規則により、ローカルクラス名とグローバルクラス名が視覚的に区別されることがわかりました。
それでは、新しいローカルクラスを取得して、 TeaListItem
コンポーネントの<li>
に割り当てましょう。
<li className={style.TeaListComponent}>
背景色の線(つまりvar(--color, var(--off-white));
)について疑問に思われるかもしれません。 このスニペットの意味は、デフォルトでは背景が--off-white
値になるということです。 ただし、カードに--color
カスタムプロパティを設定すると、代わりにその値が上書きされて選択されます。
最初は、すべてのカードを--off-white
にしますが、後で個々のカードの値を変更したい場合があります。 これは、Reactの小道具と非常によく似ています。 デフォルト値を設定できますが、特定の状況で他の値を選択できるスロットを作成します。 したがって、 CSSのバージョンのpropsのようなCSSカスタムプロパティについて考えることをお勧めします。
画像がコンテナ内にとどまるようにしたいので、スタイルはまだ見栄えがよくありません。 layout="fill"
プロパティを持つNext.jsのImageコンポーネントは、 position: absolute;
を取得します。 フレームワークから、position:relative;のコンテナに入れることでサイズを制限できます。
TeaListItem.module.cssに新しいクラスを追加しましょう:
.ImageContainer { position: relative; width: 100%; height: 10em; overflow: hidden; }
次に、 <Image>
を含む<div>
にclassName={styles.ImageContainer}
を追加しましょう。 CSSモジュール内にあるため、 ImageContainer
などの比較的「単純な」名前を使用します。したがって、外部スタイルとの競合について心配する必要はありません。
最後に、テキストの横に少しパディングを追加したいので、最後のクラスを1つ追加して、デザイントークンとして設定した間隔変数に依存しましょう。
.Title { padding-left: var(--space-sm); padding-right: var(--space-sm); }
このクラスを、名前と説明を含む<div>
に追加できます。 今、私たちのカードはそれほど悪くはありません:
グローバルスタイルとローカルスタイルの組み合わせ
次に、カードをグリッドレイアウトで表示します。 この場合、私たちはローカルスタイルとグローバルスタイルの境界にいます。 確かに、 TeaList
コンポーネントに直接レイアウトをコーディングすることができます。 しかし、リストをグリッドレイアウトに変換するユーティリティクラスがあると、他のいくつかの場所で役立つ可能性があることも想像できます。
ここでグローバルなアプローチを取り、 styles/utilities.cssに新しいユーティリティクラスを追加しましょう。
.grid { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--min-item-width, 30ch), 1fr)); gap: var(--space-md); }
これで、任意のリストに.grid
クラスを追加でき、自動的にレスポンシブなグリッドレイアウトが得られます。 --min-item-width
カスタムプロパティ(デフォルトでは30ch
)を変更して、各要素の最小幅を変更することもできます。
注:小道具のようなカスタムプロパティを考えることを忘れないでください! この構文がなじみのないように見える場合は、Chris Coyierによる「 minmax()
およびmin()
を使用した本質的にレスポンシブなCSSグリッド」を確認できます。
このスタイルはグローバルに記述されているため、 TeaList
コンポーネントにclassName="grid"
を追加するのに特別なことは必要ありません。 しかし、このグローバルなスタイルをいくつかの追加のローカルストアと組み合わせたいとしましょう。 たとえば、もう少し「お茶の美学」を取り入れて、他のすべてのカードの背景を緑色にします。 新しいcomponents/TeaList/TeaList.module.cssファイルを作成するだけです。
.TeaList > :nth-child(even) { --color: var(--green); }
TeaListItem
コンポーネントで--color custom
プロパティを作成した方法を覚えていますか? さて、今、私たちは特定の状況下でそれを設定することができます。 CSSモジュール内で子セレクターを引き続き使用できることに注意してください。また、別のモジュール内でスタイル設定されている要素を選択することは重要ではありません。 したがって、ローカルコンポーネントスタイルを使用して子コンポーネントに影響を与えることもできます。 これは、CSSカスケードを利用できるため、バグではなく機能です。 この効果を別の方法で再現しようとすると、3行のCSSではなく、ある種のJavaScriptスープになってしまう可能性があります。
次に、ローカルの.TeaList
クラスを追加しながら、グローバルな.grid
クラスをTeaList
コンポーネントに保持するにはどうすればよいでしょうか。 これは、 style.TeaList
のようなことを実行して、CSSモジュールから.TeaList
クラスにアクセスする必要があるため、構文が少しファンキーになる可能性がある場所です。
1つのオプションは、文字列補間を使用して次のようなものを取得することです。
<ul role="list" className={`${style.TeaList} grid`}>
この小さなケースでは、これで十分かもしれません。 より多くのクラスを組み合わせて使用している場合、この構文によって脳が少し爆発することがわかったので、クラス名ライブラリを使用することを選択することがあります。 この場合、より賢明なリストになります。
<ul role="list" className={classnames(style.TeaList, "grid")}>
これでショップページが完成し、 TeaList
コンポーネントでグローバルスタイルとローカルスタイルの両方を利用できるようになりました。
バランス法
これで、スタイリングを処理するためにプレーンCSSのみを使用してティーショップを構築しました。 カスタムWebpackのセットアップ、外部ライブラリのインストールなどに何年も費やす必要がなかったことにお気づきかもしれません。 これは、使用したパターンがNext.jsですぐに使用できるためです。 さらに、 CSSのベストプラクティスを奨励し、Next.jsフレームワークアーキテクチャに自然に適合します。
私たちのCSS組織は、次の4つの重要な要素で構成されていました。
- デザイントークン、
- グローバルスタイル、
- ユーティリティクラス、
- コンポーネントスタイル。
サイトの構築を続けると、デザイントークンとユーティリティクラスのリストが増えていきます。 ユーティリティクラスとして追加する意味がないスタイルは、CSSモジュールを使用してコンポーネントスタイルに追加できます。 その結果、ローカルとグローバルのスタイリングの懸念の間で継続的なバランスを見つけることができます。 Next.jsサイトと一緒に自然に成長するパフォーマンスの高い直感的なCSSコードを生成することもできます。