Modulgränser med server-only och client-only
Förhindra att serverhemligheter och tung kod läcker in i klientbundlar med poison-pill-paket.
Modulgränser med server-only och client-only är en gratis lektion i Next.js 15 fullstack (App Router + Server Actions) på CoddyKit. Detta är lektion 3 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Next.js 15 fullstack (App Router + Server Actions), och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Next.js 15 fullstack (App Router + Server Actions) innehåller totalt 4 lektioner.
Därför är modulgränser viktiga
Next.js 15 kör JavaScript i två tydligt åtskilda miljöer: Node.js-servern och webbläsarklienten. Kod som ni skriver i app/ kan obemärkt hamna i vilket paket som helst, beroende på hur den importeras.
Utan explicita gränser kan en enda oförsiktig importkedja exponera:
- Databasuppgifter och API-nycklar som lagras i miljövariabler
- Serverbunden affärslogik som aldrig borde nå användarna
- Tunga Node.js-bibliotek (crypto, fs, net) som gör klientpaketet större
Poison-pill-paketen server-only och client-only är den idiomatiska Next.js-lösningen. De utlöser ett byggtidsfel så fort en modul passerar fel gräns, vilket fångar misstaget innan någon kod distribueras till produktion.
Installera paketen
Båda paketen publiceras av Next.js-teamet och innehåller ingen kod som körs under körning. Deras enda syfte är att fungera som sentinel-importer som bundlern känner igen.
Installera dem en gång i projektet:
npm install server-only client-onlyDe är mycket små — varje paket innehåller endast en index.js som kastar ett fel om den evalueras i fel miljö. Byggkedjan (via Reacts bundlervillkor) ser till att felet utlöses vid kompilering, inte under körning.
Ni behöver inte konfigurera något i next.config.ts. Paketen förlitar sig på exportvillkoret react-server, som Next.js ställer in automatiskt.
Markera en modul som endast avsedd för servern
Alla filer som läser serverhemligheter, kommunicerar med en databas eller använder inbyggda funktioner i Node.js bör börja med en enda import:
import 'server-only'
Om en Client Component (eller någon klientkod) någonsin importerar den här modulen avbryter Next.js bygget med ett tydligt felmeddelande som pekar på den felaktiga importen. Exemplet nedan visar ett dataåtkomstlager som aldrig får nå webbläsaren.
// lib/db.ts
import 'server-only'
import { Pool } from 'pg'
const pool = new Pool({
connectionString: process.env.DATABASE_URL, // secret — server only
})
export async function getUserById(id: string) {
const { rows } = await pool.query(
'SELECT id, name, email FROM users WHERE id = $1',
[id]
)
return rows[0] ?? null
}Vad händer när gränsen överskrids
Anta att en utvecklare av misstag importerar det serverbundna dataåtkomstlagret i en Client Component. Utan poison pill skulle hemligheten DATABASE_URL obemärkt visas i webbläsarpaketet.
Med server-only på plats stoppar Next.js-bygget omedelbart och skriver ut:
Error: This module cannot be imported from a Client Component module.
It should only be used from a Server Component.Detta gör det omöjligt att råka distribuera överträdelsen. Felet är deterministiskt — det utlöses vid varje next build och varje hot reload i next dev som passerar gränsen.
// app/dashboard/page.tsx — Server Component, safe to import lib/db
import { getUserById } from '@/lib/db'
export default async function DashboardPage() {
const user = await getUserById('user_123')
return <h1>Welcome, {user?.name}</h1>
}
// app/components/ProfileCard.tsx — Client Component
'use client'
// import { getUserById } from '@/lib/db' // ← BUILD ERROR if uncommented
export function ProfileCard({ name }: { name: string }) {
return <p>{name}</p>
}Markera en modul som endast avsedd för klienten
client-only löser det motsatta problemet. Vissa moduler är beroende av webbläsarspecifika API:er som window, document, localStorage eller webbläsarspecifika tredjeparts-SDK:er.
Om en sådan modul importeras i en Server Component kastar Node.js ett fel under körning, eftersom dessa globala webbläsarobjekt inte finns på servern. client-only omvandlar denna överraskning under körning till ett byggtidsfel.
Lägg till importen överst i alla webbläsarspecifika hjälpmoduler:
import 'client-only'
// lib/analytics.ts
import 'client-only'
// This module calls browser APIs — it must never run on the server
export function trackEvent(name: string, props?: Record<string, unknown>) {
if (typeof window === 'undefined') return // extra guard, but poison-pill fires first
window.gtag?.('event', name, props)
}
export function getStoredUserId(): string | null {
return localStorage.getItem('userId')
}Säkert mönster: serverdata skickas som props
Det vedertagna mönstret i Next.js 15 är att hämta data i en Server Component med hjälp av en server-only-modul och sedan skicka säkra, serialiserbara värden vidare till Client Components som props.
Inga hemligheter, databashandtag eller Node.js-specifika objekt passerar någonsin nätverksgränsen — endast vanlig data som säkert kan serialiseras till JSON.
// lib/user-service.ts
import 'server-only'
import { pool } from './db'
export interface UserProfile {
id: string
name: string
avatarUrl: string
}
export async function getProfile(userId: string): Promise<UserProfile | null> {
const { rows } = await pool.query(
'SELECT id, name, avatar_url FROM users WHERE id = $1',
[userId]
)
if (!rows[0]) return null
return { id: rows[0].id, name: rows[0].name, avatarUrl: rows[0].avatar_url }
}
// app/profile/page.tsx — Server Component
import { getProfile } from '@/lib/user-service'
import { AvatarCard } from '@/components/AvatarCard' // 'use client'
export default async function ProfilePage({ params }: { params: { id: string } }) {
const profile = await getProfile(params.id)
if (!profile) return <p>Not found</p>
// Only serialisable data crosses to the client
return <AvatarCard name={profile.name} avatarUrl={profile.avatarUrl} />
}Server Actions är ingen genväg runt gränsen
Server Actions är funktioner som körs på servern men anropas från klientkod. En vanlig missuppfattning är att hela modulen automatiskt skyddas när en funktion markeras med 'use server'.
Det gör den inte. Direktivet 'use server' instruerar endast Next.js att exponera funktionen som en HTTP-slutpunkt. Andra exporter i samma fil kan fortfarande läcka om de importeras direkt.
God praxis är att hålla Server Actions i särskilda actions/-filer och ändå skydda delade serverhjälpmoduler med server-only.
// app/actions/update-profile.ts
'use server'
import { getUserById } from '@/lib/db' // lib/db has 'server-only' — safe
import { revalidatePath } from 'next/cache'
export async function updateProfileAction(formData: FormData) {
const name = formData.get('name') as string
const userId = formData.get('userId') as string
// Business logic runs entirely on the server
const existing = await getUserById(userId)
if (!existing) throw new Error('User not found')
// db update omitted for brevity
revalidatePath('/profile')
}
// app/profile/edit/page.tsx — this is a Client Component form
'use client'
import { updateProfileAction } from '@/app/actions/update-profile'
export function EditForm({ userId }: { userId: string }) {
return (
<form action={updateProfileAction}>
<input type="hidden" name="userId" value={userId} />
<input name="name" placeholder="New name" />
<button type="submit">Save</button>
</form>
)
}Organisera mappen lib/ efter gräns
En förutsägbar mappkonvention eliminerar gissningar om vilken miljö en hjälpmodul är avsedd för. En vanligt använd struktur ser ut så här:
lib/server/— varje fil börjar medimport 'server-only'lib/client/— varje fil börjar medimport 'client-only'lib/shared/— rena funktioner utan miljöspecifika importer (säkra i båda miljöerna)
Med den här strukturen kan en kodgranskare omedelbart se om en ny hjälpmodul ligger rätt, utan att ens läsa innehållet. Lintregler i CI kan också säkerställa att filer i lib/server/ innehåller sentinel-importen.
// lib/shared/format.ts — no sentinel needed, works anywhere
export function formatCurrency(amount: number, currency = 'USD'): string {
return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)
}
// lib/server/stripe.ts
import 'server-only'
import Stripe from 'stripe'
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-11-20.acacia',
})
// lib/client/toast.ts
import 'client-only'
import { toast } from 'sonner'
export function showSuccess(msg: string) {
toast.success(msg)
}Kombinera med TypeScript-sökvägsalias
TypeScript-sökvägsalias i tsconfig.json gör gränsmedvetna importer smidiga och konsekventa i hela kodbasen. Ni kan koda in gränsen direkt i aliasnamnet, så att importer blir självförklarande.
// tsconfig.json (relevant excerpt)
// {
// "compilerOptions": {
// "paths": {
// "@server/*": ["./lib/server/*"],
// "@client/*": ["./lib/client/*"],
// "@shared/*": ["./lib/shared/*"]
// }
// }
// }
// Usage in a Server Component:
import { stripe } from '@server/stripe'
import { formatCurrency } from '@shared/format'
// Usage in a Client Component:
import { showSuccess } from '@client/toast'
import { formatCurrency } from '@shared/format'
// Attempting to import @server/* in a Client Component
// triggers the server-only build error immediately.Tredjepartspaket utan direktiv
Många npm-paket (särskilt äldre paket) innehåller inget direktiv som 'use client' eller 'use server'. Next.js-bundlern använder en heuristik: om ett paket saknar direktiv behandlas det som delat och kan användas i båda miljöerna.
Detta kan vara ett problem när ett paket använder globala webbläsarobjekt internt. Lösningen är att kapsla in det i en egen lib/client/-modul som är markerad med client-only, så att importkedjan blir explicit och möjlig att granska.
// lib/client/chart-wrapper.ts
import 'client-only'
// chart.js has no 'use client' directive but uses window internally
import { Chart, registerables } from 'chart.js'
Chart.register(...registerables)
export { Chart }
export type { ChartConfiguration } from 'chart.js'
// components/RevenueChart.tsx
'use client'
import { Chart } from '@client/chart-wrapper' // boundary enforced
import { useEffect, useRef } from 'react'
export function RevenueChart({ data }: { data: number[] }) {
const ref = useRef<HTMLCanvasElement>(null)
useEffect(() => {
if (!ref.current) return
const ctx = ref.current.getContext('2d')!
new Chart(ctx, { type: 'line', data: { labels: data.map(String), datasets: [{ data }] } })
}, [data])
return <canvas ref={ref} />
}Verifiera era paket med @next/bundle-analyzer
Poison-pill-paket förhindrar oavsiktligt läckage vid byggtid, men det är fortfarande värdefullt att verifiera era paket visuellt. Paketet @next/bundle-analyzer genererar ett interaktivt treemap-diagram över varje modul i varje paket.
Använd det för att bekräfta att serverbundna moduler (databasklienter, Stripe-SDK:er, tunga Node.js-bibliotek) saknas i klient-chunken och vice versa. Om en modul som innehåller hemligheter visas i klientpaketet trots sentinel-importen betyder det vanligtvis att en dynamisk import eller en vidareexport kringgick kontrollen.
// next.config.ts
import type { NextConfig } from 'next'
import withBundleAnalyzer from '@next/bundle-analyzer'
const withAnalyzer = withBundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
})
const nextConfig: NextConfig = {
// your existing config
}
export default withAnalyzer(nextConfig)
// Run analysis:
// ANALYZE=true next build
// Opens two browser tabs:
// - client.html (browser bundle — check for leaked server deps)
// - server.html (server bundle)Kunskapskontroll: server-only jämfört med client-only
Välj det bästa svaret på frågan nedan.
Sammanfattning: modulgränser med server-only och client-only
I den här lektionen lärde ni er att införa tydliga modulgränser i en Next.js 15-applikation:
import 'server-only'— gör modulen till ett byggtidsfel om den importeras från klientkod. Använd det för databasklienter, hjälpmoduler som läser hemligheter och kod som endast fungerar i Node.js.import 'client-only'— motsvarande mönster för webbläsarspecifika moduler, som fångar serverimporter innan de orsakar krascher under körning.- Det säkra dataflödesmönstret: hämta data i en Server Component med server-only-hjälpmoduler och skicka sedan serialiserbara props till Client Components — inga hemligheter eller Node-API:er passerar över nätverket.
- Organisera
lib/i underkatalogernaserver/,client/ochshared/, och anpassa TypeScript-sökvägsaliasen så att gränserna blir självförklarande. - Kapsla in tredjepartspaket som saknar direktiv i egna gränsmarkerade moduler, så att importgrafen förblir möjlig att granska.
- Kombinera poison-pill-paket med
@next/bundle-analyzerför att visuellt verifiera att inga serverbundna beroenden förekommer i klient-chunks.
Det kostar nästan ingenting att lägga till de här två paketen och de eliminerar en hel kategori av säkerhets- och prestandafel innan de någonsin når produktion.
Lär dig TypeScript med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 22
- Lektioner
- 88
Vanliga frågor
Är lektionen ”Modulgränser med server-only och client-only” gratis?
Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Next.js 15 fullstack (App Router + Server Actions), inklusive ”Modulgränser med server-only och client-only”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Next.js 15 fullstack (App Router + Server Actions) innehåller totalt 4 lektioner.
Vad lär jag mig i ”Modulgränser med server-only och client-only”?
Förhindra att serverhemligheter och tung kod läcker in i klientbundlar med poison-pill-paket. Ni övar på Next.js 15 fullstack (App Router + Server Actions) med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig Next.js 15 fullstack (App Router + Server Actions)?
Du behöver inga förkunskaper. Utbildningen i Next.js 15 fullstack (App Router + Server Actions) på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.
Hur lång tid tar lektionen ”Modulgränser med server-only och client-only”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här Next.js 15 fullstack (App Router + Server Actions)-lektionen?
Ja. Varje Next.js 15 fullstack (App Router + Server Actions)-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Analysera och minska klientens bundle
- Fördjupning i Turbopack- och kompilatorkonfiguration
- Modulgränser med server-only och client-only
- Dynamiska importer, koddelning och lazy hydration