Penataan Gaya Global vs. Lokal Di Next.js
Diterbitkan: 2022-03-10Saya memiliki pengalaman hebat menggunakan Next.js untuk mengelola proyek front-end yang kompleks. Next.js memiliki opini tentang cara mengatur kode JavaScript, tetapi tidak memiliki opini bawaan tentang cara mengatur CSS.
Setelah bekerja dalam kerangka kerja, saya telah menemukan serangkaian pola organisasi yang saya yakini sesuai dengan filosofi panduan Next.js dan menerapkan praktik CSS terbaik. Dalam artikel ini, kita akan membuat situs web (toko teh!) bersama-sama untuk mendemonstrasikan pola-pola ini.
Catatan : Anda mungkin tidak memerlukan pengalaman Next.js sebelumnya, meskipun akan lebih baik jika Anda memiliki pemahaman dasar tentang React dan terbuka untuk mempelajari beberapa teknik CSS baru.
Menulis CSS “Kuno”
Saat pertama kali melihat Next.js, kita mungkin tergoda untuk mempertimbangkan menggunakan beberapa jenis library CSS-in-JS. Meskipun mungkin ada manfaat tergantung pada proyeknya, CSS-in-JS memperkenalkan banyak pertimbangan teknis. Ini membutuhkan penggunaan perpustakaan eksternal baru, yang menambah ukuran bundel. CSS-in-JS juga dapat memiliki dampak kinerja dengan menyebabkan render dan dependensi tambahan pada status global.
Bacaan yang direkomendasikan: “ Biaya Performa Tak Terlihat Dari Perpustakaan CSS-in-JS Modern Dalam Aplikasi React)” oleh Aggelos Arvanitakis
Selain itu, inti dari penggunaan library seperti Next.js adalah untuk merender aset secara statis bila memungkinkan, jadi tidak masuk akal untuk menulis JS yang perlu dijalankan di browser untuk menghasilkan CSS.
Ada beberapa pertanyaan yang harus kita pertimbangkan saat mengatur gaya dalam Next.js:
Bagaimana kita bisa masuk ke dalam konvensi/praktik terbaik dari kerangka kerja?
Bagaimana kita dapat menyeimbangkan masalah gaya "global" (font, warna, tata letak utama, dan sebagainya) dengan masalah "lokal" (gaya mengenai komponen individual)?
Jawaban yang saya dapatkan untuk pertanyaan pertama adalah dengan menulis CSS kuno yang bagus . Next.js tidak hanya mendukung melakukannya tanpa penyiapan tambahan; itu juga menghasilkan hasil yang berkinerja dan statis.
Untuk mengatasi masalah kedua, saya mengambil pendekatan yang dapat diringkas dalam empat bagian:
- Token desain
- Gaya global
- Kelas utilitas
- Gaya komponen
Saya berhutang budi pada ide Andy Bell tentang CUBE CSS (“Komposisi, Utilitas, Blok, Pengecualian”) di sini. Jika Anda belum pernah mendengar tentang prinsip organisasi ini sebelumnya, saya sarankan untuk memeriksa situs atau fitur resminya di Smashing Podcast. Salah satu prinsip yang akan kita ambil dari CUBE CSS adalah gagasan bahwa kita harus merangkul daripada takut pada kaskade CSS. Mari pelajari teknik ini dengan menerapkannya ke proyek situs web.
Mulai
Kami akan membangun toko teh karena teh itu enak. Kita akan mulai dengan menjalankan yarn create next-app
untuk membuat proyek Next.js baru. Kemudian, kami akan menghapus semua yang ada di styles/ directory
(semuanya adalah kode sampel).
Catatan : Jika Anda ingin mengikuti proyek yang sudah selesai, Anda dapat memeriksanya di sini.
Token Desain
Di hampir semua penyiapan CSS, ada manfaat yang jelas untuk menyimpan semua nilai yang dibagikan secara global dalam variabel . Jika klien meminta warna untuk diubah, menerapkan perubahan adalah satu baris daripada kekacauan besar-besaran menemukan dan mengganti. Akibatnya, bagian penting dari penyiapan CSS Next.js kami akan menyimpan semua nilai di seluruh situs sebagai token desain .
Kami akan menggunakan Properti Kustom CSS bawaan untuk menyimpan token ini. (Jika Anda tidak terbiasa dengan sintaks ini, Anda dapat melihat "Panduan Strategi Untuk Properti Kustom CSS".) Saya harus menyebutkan bahwa (dalam beberapa proyek) saya telah memilih untuk menggunakan variabel SASS/SCSS untuk tujuan ini. Saya belum menemukan keuntungan nyata, jadi saya biasanya hanya menyertakan SASS dalam sebuah proyek jika saya merasa membutuhkan fitur SASS lainnya (campuran, iterasi, mengimpor file, dan sebagainya). Sebaliknya, properti kustom CSS juga berfungsi dengan kaskade dan dapat diubah seiring waktu daripada dikompilasi secara statis. Jadi, untuk hari ini, mari kita tetap menggunakan CSS biasa .
Di direktori styles/
kita, mari buat file design_tokens.css baru:
: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; }
Tentu saja, daftar ini dapat dan akan bertambah seiring waktu. Setelah kita menambahkan file ini, kita perlu melompat ke file pages/_app.jsx kita, yang merupakan tata letak utama untuk semua halaman kita, dan menambahkan:
import '../styles/design_tokens.css'
Saya suka menganggap token desain sebagai perekat yang menjaga konsistensi di seluruh proyek. Kami akan mereferensikan variabel-variabel ini pada skala global, serta dalam komponen individu, memastikan bahasa desain yang terpadu.
Gaya Global
Selanjutnya, mari tambahkan halaman ke situs web kita! Mari masuk ke file pages/index.jsx (ini adalah beranda kami). Kami akan menghapus semua boilerplate dan menambahkan sesuatu seperti:
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> }
Sayangnya, ini akan terlihat sangat sederhana, jadi mari kita atur beberapa gaya global untuk elemen dasar , misalnya <h1>
. (Saya suka menganggap gaya ini sebagai "default global yang masuk akal".) Kita mungkin menimpanya dalam kasus-kasus tertentu, tetapi itu adalah tebakan yang bagus tentang apa yang kita inginkan jika kita tidak melakukannya.
Saya akan meletakkan ini di file styles/globals.css (yang datang secara default dari 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%; }
Tentu saja, versi ini cukup mendasar, tetapi file globals.css saya biasanya tidak perlu terlalu besar. Di sini, saya menata elemen HTML dasar (judul, isi, tautan, dan sebagainya). Tidak perlu membungkus elemen-elemen ini dalam komponen React atau menambahkan kelas secara konstan hanya untuk memberikan gaya dasar.
Saya juga menyertakan pengaturan ulang gaya browser default . Kadang-kadang, saya akan memiliki beberapa gaya tata letak seluruh situs untuk memberikan "footer lengket", misalnya, tetapi mereka hanya termasuk di sini jika semua halaman berbagi tata letak yang sama. Jika tidak, itu perlu dicakup di dalam komponen individu.
Saya selalu menyertakan beberapa jenis :focus
styling untuk secara jelas menunjukkan elemen interaktif untuk pengguna keyboard saat fokus. Yang terbaik adalah menjadikannya bagian integral dari DNA desain situs!
Sekarang, situs web kami mulai terbentuk:
Kelas Utilitas
Salah satu area di mana beranda kita pasti dapat meningkatkan adalah bahwa teks saat ini selalu memanjang ke sisi layar, jadi mari kita batasi lebarnya. Kami membutuhkan tata letak ini di halaman ini, tetapi saya membayangkan bahwa kami mungkin juga membutuhkannya di halaman lain. Ini adalah kasus penggunaan yang bagus untuk kelas utilitas!
Saya mencoba menggunakan kelas utilitas dengan hemat daripada sebagai pengganti hanya menulis CSS. Kriteria pribadi saya ketika masuk akal untuk menambahkan satu ke proyek adalah:
- Saya membutuhkannya berulang kali;
- Itu melakukan satu hal dengan baik;
- Ini berlaku di berbagai komponen atau halaman yang berbeda.
Saya pikir kasus ini memenuhi ketiga kriteria, jadi mari buat file CSS baru styles/utilities.css dan tambahkan:
.lockup { max-width: 90ch; margin: 0 auto; }
Lalu mari tambahkan import '../styles/utilities.css'
ke pages/_app.jsx kita . Terakhir, mari kita ubah tag <main>
di pages/index.jsx kita menjadi <main className="lockup">
.
Sekarang, halaman kami semakin menyatu. Karena kami menggunakan properti max-width
, kami tidak memerlukan kueri media apa pun untuk membuat tata letak seluler kami responsif. Dan, karena kami menggunakan unit pengukuran ch
— yang setara dengan lebar satu karakter — ukuran kami dinamis dengan ukuran font browser pengguna.
Seiring pertumbuhan situs web kami, kami dapat terus menambahkan lebih banyak kelas utilitas. Saya mengambil pendekatan yang cukup utilitarian di sini: Jika saya sedang bekerja dan menemukan saya membutuhkan kelas lain untuk warna atau sesuatu, saya menambahkannya. Saya tidak menambahkan setiap kemungkinan kelas di bawah matahari — itu akan membuat ukuran file CSS membengkak dan membuat kode saya membingungkan. Terkadang, dalam proyek yang lebih besar, saya suka memecahnya menjadi direktori styles/utilities/
dengan beberapa file berbeda; terserah kebutuhan proyek.
Kita dapat menganggap kelas utilitas sebagai toolkit kita yang umum, perintah penataan gaya berulang yang dibagikan secara global. Mereka membantu mencegah kami terus-menerus menulis ulang CSS yang sama di antara komponen yang berbeda.
Gaya Komponen
Kami telah menyelesaikan beranda kami untuk saat ini, tetapi kami masih perlu membangun bagian dari situs web kami: toko online. Tujuan kami di sini adalah menampilkan petak kartu dari semua teh yang ingin kami jual , jadi kami perlu menambahkan beberapa komponen ke situs kami.
Mari kita mulai dengan menambahkan halaman baru di pages/shop.jsx :
export default function Shop() { return <main> <div className="lockup"> <h1>Shop Our Teas</h1> </div> </main> }
Kemudian, kita perlu teh untuk dipajang. Kami akan menyertakan nama, deskripsi, dan gambar (di direktori publik/) untuk setiap teh:
const teas = [ { name: "Oolong", description: "A partially fermented tea.", image: "/oolong.jpg" }, // ... ]
Catatan : Ini bukan artikel tentang pengambilan data, jadi kami mengambil rute yang mudah dan mendefinisikan sebuah array di awal file.
Selanjutnya, kita perlu mendefinisikan komponen untuk menampilkan teh kita. Mari kita mulai dengan membuat components/
direktori (Next.js tidak membuat ini secara default). Kemudian, mari tambahkan direktori components/TeaList
. Untuk komponen apa pun yang akhirnya membutuhkan lebih dari satu file, saya biasanya meletakkan semua file terkait di dalam folder. Melakukannya mencegah components/
folder kami menjadi tidak dapat dinavigasi.
Sekarang, mari tambahkan file component/TeaList/TeaList.jsx kita :
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
Tujuan dari komponen ini adalah untuk mengulangi teh kita dan untuk menampilkan item daftar untuk masing-masing, jadi sekarang mari kita definisikan komponen component/TeaList/TeaListItem.jsx kita :
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
Perhatikan bahwa kami menggunakan komponen gambar bawaan Next.js. Saya menyetel atribut alt
ke string kosong karena gambarnya murni dekoratif dalam kasus ini; kami ingin menghindari mengganggu pengguna pembaca layar dengan deskripsi gambar yang panjang di sini.
Terakhir, mari kita buat file component/TeaList/index.js , agar komponen kita mudah untuk diimpor secara eksternal:
import TeaList from './TeaList' import TeaListItem from './TeaListItem' export { TeaListItem } export default TeaList
Lalu, mari gabungkan semuanya dengan menambahkan import TeaList dari ../components/TeaList
dan <TeaList teas={teas} />
ke halaman Toko kita. Sekarang, teh kami akan muncul dalam daftar, tetapi tidak akan terlalu cantik.
Menempatkan Gaya Dengan Komponen Melalui Modul CSS
Mari kita mulai dengan menata kartu kita (komponen TeaListLitem
). Sekarang, untuk pertama kalinya dalam proyek kami, kami ingin menambahkan gaya yang khusus hanya untuk satu komponen. Mari buat file baru component/TeaList/TeaListItem.module.css .
Anda mungkin bertanya-tanya tentang modul dalam ekstensi file. Ini adalah Modul CSS . Next.js mendukung modul CSS dan menyertakan beberapa dokumentasi bagus tentangnya. Saat kita menulis nama kelas dari modul CSS seperti .TeaListItem
, secara otomatis akan diubah menjadi sesuatu yang lebih seperti . TeaListItem_TeaListItem__TFOk_
. TeaListItem_TeaListItem__TFOk_
dengan banyak karakter tambahan yang ditempelkan. Akibatnya, kita dapat menggunakan nama kelas apa pun yang kita inginkan tanpa khawatir itu akan bertentangan dengan nama kelas lain di tempat lain di situs kita.
Keuntungan lain dari modul CSS adalah kinerja. Next.js menyertakan fitur impor dinamis. next/dynamic memungkinkan kita memuat komponen dengan lambat sehingga kodenya hanya dimuat saat dibutuhkan, daripada menambahkan ke seluruh ukuran bundel. Jika kita mengimpor gaya lokal yang diperlukan ke dalam komponen individual, maka pengguna juga dapat dengan malas memuat CSS untuk komponen yang diimpor secara dinamis . Untuk proyek besar, kami dapat memilih untuk malas memuat sebagian besar kode kami dan hanya memuat JS/CSS yang paling diperlukan di muka. Akibatnya, saya biasanya membuat file Modul CSS baru untuk setiap komponen baru yang membutuhkan penataan lokal.
Mari kita mulai dengan menambahkan beberapa gaya awal ke file kita:
.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); }
Kemudian, kita dapat mengimpor gaya dari ./TeaListItem.module.css
di komponen TeaListitem
kita. Variabel style masuk seperti objek JavaScript, jadi kita bisa mengakses style.TeaListItem.
Catatan : Nama kelas kita tidak perlu menggunakan huruf kapital. Saya telah menemukan bahwa konvensi nama kelas dengan huruf besar di dalam modul (dan huruf kecil di luar) membedakan nama kelas lokal vs global secara visual.
Jadi, mari kita ambil kelas lokal baru kita dan tetapkan ke <li>
di komponen TeaListItem
kita:
<li className={style.TeaListComponent}>
Anda mungkin bertanya-tanya tentang garis warna latar belakang (yaitu var(--color, var(--off-white));
). Yang dimaksud dengan cuplikan ini adalah bahwa secara default latar belakang akan menjadi nilai --off-white
kami. Namun, jika kita menetapkan properti kustom --color
pada kartu, itu akan menimpa dan memilih nilai itu sebagai gantinya.
Pada awalnya, kita ingin semua kartu kita menjadi --off-white
, tetapi kita mungkin ingin mengubah nilai untuk masing-masing kartu nanti. Ini bekerja sangat mirip dengan props di React. Kami dapat menetapkan nilai default tetapi membuat slot di mana kami dapat memilih nilai lain dalam keadaan tertentu. Jadi, saya mendorong kita untuk memikirkan properti kustom CSS seperti versi CSS dari props .
Gaya tetap tidak akan terlihat bagus karena kami ingin memastikan bahwa gambar tetap berada di dalam wadahnya. Komponen Gambar Next.js dengan prop layout="fill"
mendapat position: absolute;
dari kerangka, jadi kita bisa membatasi ukurannya dengan memasukkan wadah dengan posisi: relatif;.
Mari tambahkan kelas baru ke TeaListItem.module.css kita :
.ImageContainer { position: relative; width: 100%; height: 10em; overflow: hidden; }
Dan kemudian mari tambahkan className={styles.ImageContainer}
pada <div>
yang berisi <Image>
kita. Saya menggunakan nama yang relatif "sederhana" seperti ImageContainer
karena kita berada di dalam modul CSS, jadi kita tidak perlu khawatir akan bentrok dengan gaya luar.
Terakhir, kita ingin menambahkan sedikit padding di sisi teks, jadi mari tambahkan satu kelas terakhir dan bergantung pada variabel spasi yang kita siapkan sebagai token desain:
.Title { padding-left: var(--space-sm); padding-right: var(--space-sm); }
Kita dapat menambahkan kelas ini ke <div>
yang berisi nama dan deskripsi kita. Sekarang, kartu kami tidak terlihat buruk:
Menggabungkan Gaya Global dan Lokal
Selanjutnya, kami ingin kartu kami ditampilkan dalam tata letak kotak. Dalam hal ini, kita hanya berada di perbatasan antara gaya lokal dan global. Kami tentu saja dapat mengkodekan tata letak kami secara langsung pada komponen TeaList
. Tapi, saya juga bisa membayangkan bahwa memiliki kelas utilitas yang mengubah daftar menjadi tata letak kotak dapat berguna di beberapa tempat lain.
Mari kita ambil pendekatan global di sini dan tambahkan kelas utilitas baru di styles/utilities.css kita :
.grid { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--min-item-width, 30ch), 1fr)); gap: var(--space-md); }
Sekarang, kita dapat menambahkan kelas .grid
pada daftar apa pun, dan kita akan mendapatkan tata letak grid yang responsif secara otomatis. Kami juga dapat mengubah properti kustom --min-item-width
(secara default 30ch
) untuk mengubah lebar minimum setiap elemen.
Catatan : Ingatlah untuk memikirkan properti khusus seperti properti! Jika sintaks ini terlihat asing, Anda dapat melihat “Kisi CSS Responsif Intrinsik Dengan minmax()
Dan min()
” oleh Chris Coyier.
Karena kita telah menulis gaya ini secara global, tidak memerlukan kemewahan untuk menambahkan className="grid"
ke komponen TeaList
kita. Namun, katakanlah kita ingin menggabungkan gaya global ini dengan beberapa toko lokal tambahan. Misalnya, kami ingin membawa sedikit lebih banyak "estetika teh" dan membuat setiap kartu lainnya memiliki latar belakang hijau. Yang perlu kita lakukan adalah membuat file component/TeaList/TeaList.module.css baru :
.TeaList > :nth-child(even) { --color: var(--green); }
Ingat bagaimana kami membuat properti --color custom
--color pada komponen TeaListItem
kami? Nah, sekarang kita bisa mengaturnya dalam keadaan tertentu. Perhatikan bahwa kita masih dapat menggunakan pemilih anak dalam modul CSS, dan tidak masalah jika kita memilih elemen yang ditata di dalam modul yang berbeda. Jadi, kita juga dapat menggunakan gaya komponen lokal untuk memengaruhi komponen turunan. Ini adalah fitur daripada bug, karena memungkinkan kita untuk memanfaatkan kaskade CSS ! Jika kami mencoba mereplikasi efek ini dengan cara lain, kami mungkin akan berakhir dengan semacam sup JavaScript daripada tiga baris CSS.
Lalu, bagaimana kita bisa mempertahankan kelas .grid
global pada komponen TeaList
kita sementara juga menambahkan kelas .TeaList
lokal? Di sinilah sintaks bisa menjadi sedikit funky karena kita harus mengakses kelas .TeaList
kita dari modul CSS dengan melakukan sesuatu seperti style.TeaList
.
Salah satu opsi adalah menggunakan interpolasi string untuk mendapatkan sesuatu seperti:
<ul role="list" className={`${style.TeaList} grid`}>
Dalam kasus kecil ini, ini mungkin cukup baik. Jika kita mencampur-dan-mencocokkan lebih banyak kelas, saya menemukan bahwa sintaks ini membuat otak saya meledak sedikit, jadi saya kadang-kadang akan memilih untuk menggunakan perpustakaan classnames. Dalam hal ini, kita berakhir dengan daftar yang tampak lebih masuk akal:
<ul role="list" className={classnames(style.TeaList, "grid")}>
Sekarang, kami telah menyelesaikan halaman Toko kami, dan kami telah membuat komponen TeaList
kami memanfaatkan gaya global dan lokal.
UU Keseimbangan
Kami sekarang telah membangun kedai teh kami hanya menggunakan CSS biasa untuk menangani penataan. Anda mungkin telah memperhatikan bahwa kami tidak perlu menghabiskan waktu lama berurusan dengan pengaturan Webpack kustom, menginstal perpustakaan eksternal, dan sebagainya. Itu karena pola yang kami gunakan bekerja dengan Next.js di luar kotak. Selain itu, mereka mendorong praktik CSS terbaik dan secara alami cocok dengan arsitektur kerangka kerja Next.js.
Organisasi CSS kami terdiri dari empat bagian utama:
- Token desain,
- gaya global,
- kelas utilitas,
- Gaya komponen.
Saat kami terus membangun situs kami, daftar token desain dan kelas utilitas kami akan bertambah. Penataan gaya apa pun yang tidak masuk akal untuk ditambahkan sebagai kelas utilitas, dapat kita tambahkan ke gaya komponen menggunakan modul CSS. Hasilnya, kami dapat menemukan keseimbangan berkelanjutan antara masalah gaya lokal dan global. Kami juga dapat menghasilkan kode CSS intuitif dan berkinerja yang tumbuh secara alami di samping situs Next.js kami.