Merhaba arkadaşlar, bugün başımı çok ağrıtan bir sorunu ve onun en temiz çözümünü paylaşacağım. Next.js ile server-side rendering (SSR) yaparken, styled-components veya Emotion gibi CSS-in-JS kütüphaneleri kullandığımızda, ilk render'da stil kayboluyor ve sayfa bir anlığına çıplak (FOUC - Flash of Unstyled Content) görünüyordu. Bu hatayı ilk gördüğümde, "Ben bunu production'a nasıl çıkaracağım?" diye kafayı yemiştim. Neyse ki, Next.js'in kendi dokümantasyonunda ve community'de bu işin püf noktaları var.
Karşılaştığım Sorun ve Nedenleri
Sorunun kökü şu: Server'da React bileşenlerini render edip HTML olarak istemciye gönderiyoruz. Ancak, styled-components'in stilleri, React bileşeni render edilirken oluşuyor. Eğer server'da oluşturulan bu stilleri HTML dokümanına eklemezsek, tarayıcı önce stil[siz] HTML'i görüyor, sonra JavaScript yüklenip çalıştığında stiller enjekte ediliyor. Bu da o çirkin "flash" efektine neden oluyor.
Çözüm: Stilleri Server'dan Göndermek
Çözüm, server tarafında oluşturulan stilleri toplayıp, HTML çıktısının <head> kısmına yerleştirmek. Next.js'te bunun için _document.js (veya _document.tsx) dosyasını özelleştirmemiz gerekiyor. Bu dosya, sunucu tarafında ve istemci tarafında bir kez render edilen sayfa şablonumuz.
İşte benim kullandığım en temiz ve güvenilir yöntem:
Bu kod ne yapıyor? getInitialProps içinde, ServerStyleSheet oluşturuyoruz ve ctx.renderPage metodunu, uygulamamızı (<App />) bu style sheet'in içine sarmalayacak şekilde override ediyoruz. Bu sayfa render edilirken oluşan tüm stiller bu sheet'te toplanıyor. Sonrasında, dökümanın ilk props'larına, bu sheet'ten gelen style elementlerini ekliyoruz. Next.js bunu otomatik olarak <head> içine yerleştiriyor.
Emotion Kullanıyorsanız
Eğer Emotion kullanıyorsanız, yaklaşım çok benzer. Sadece import ve metod isimleri değişiyor. Aşağıdaki gibi bir yapı kurabilirsiniz:
Sonuç ve Performans
Bu yöntemi uyguladıktan sonra, sayfalarım artık sunucudan stil[siz] gelmiyor. İlk HTML yüklendiği anda, tüm CSS kuralları da <style> tag'leri içinde hazır oluyor. Bu, kullanıcı deneyimi için çok kritik bir iyileştirme. Ayrıca, Lighthouse skorlarında "First Contentful Paint" gibi metriklerde de olumlu etkisini görebilirsiniz.
Peki ya siz? Next.js ile CSS-in-JS kullanırken bu tarz bir sorunla karşılaştınız mı? styled-components mi yoksa Emotion mu tercih ediyorsunuz? Ya da bu işi halletmek için farklı, daha sade bir yönteminiz var mı? Yorumlarda buluşalım!
Sorunun kökü şu: Server'da React bileşenlerini render edip HTML olarak istemciye gönderiyoruz. Ancak, styled-components'in stilleri, React bileşeni render edilirken oluşuyor. Eğer server'da oluşturulan bu stilleri HTML dokümanına eklemezsek, tarayıcı önce stil[siz] HTML'i görüyor, sonra JavaScript yüklenip çalıştığında stiller enjekte ediliyor. Bu da o çirkin "flash" efektine neden oluyor.
Çözüm, server tarafında oluşturulan stilleri toplayıp, HTML çıktısının <head> kısmına yerleştirmek. Next.js'te bunun için _document.js (veya _document.tsx) dosyasını özelleştirmemiz gerekiyor. Bu dosya, sunucu tarafında ve istemci tarafında bir kez render edilen sayfa şablonumuz.
İşte benim kullandığım en temiz ve güvenilir yöntem:
JavaScript:
// pages/_document.js
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
// Emotion için: import { extractCritical } from '@emotion/server'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}
Bu kod ne yapıyor? getInitialProps içinde, ServerStyleSheet oluşturuyoruz ve ctx.renderPage metodunu, uygulamamızı (<App />) bu style sheet'in içine sarmalayacak şekilde override ediyoruz. Bu sayfa render edilirken oluşan tüm stiller bu sheet'te toplanıyor. Sonrasında, dökümanın ilk props'larına, bu sheet'ten gelen style elementlerini ekliyoruz. Next.js bunu otomatik olarak <head> içine yerleştiriyor.
Eğer Emotion kullanıyorsanız, yaklaşım çok benzer. Sadece import ve metod isimleri değişiyor. Aşağıdaki gibi bir yapı kurabilirsiniz:
JavaScript:
// pages/_document.js (Emotion örneği)
import Document from 'next/document'
import { extractCritical } from '@emotion/server'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
const { css, ids } = extractCritical(initialProps.html)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
<style
data-emotion-css={ids.join(' ')}
dangerouslySetInnerHTML={{ __html: css }}
/>
</>
),
}
}
}
Bu yöntemi uyguladıktan sonra, sayfalarım artık sunucudan stil[siz] gelmiyor. İlk HTML yüklendiği anda, tüm CSS kuralları da <style> tag'leri içinde hazır oluyor. Bu, kullanıcı deneyimi için çok kritik bir iyileştirme. Ayrıca, Lighthouse skorlarında "First Contentful Paint" gibi metriklerde de olumlu etkisini görebilirsiniz.
Peki ya siz? Next.js ile CSS-in-JS kullanırken bu tarz bir sorunla karşılaştınız mı? styled-components mi yoksa Emotion mu tercih ediyorsunuz? Ya da bu işi halletmek için farklı, daha sade bir yönteminiz var mı? Yorumlarda buluşalım!