Poznaj problem z html { scroll-behavior: smooth } w Next.js App Router i dowiedz się, jak prawidłowo obsłużyć płynne scrollowanie bez psujących efektów.
Jak działa scroll-behavior: smooth
Właściwość CSS scroll-behavior kontroluje, jak przeglądarka wykonuje każde programatyczne przewinięcie – nie tylko kliknięcia w anchory. Dotyczy to również wywołań window.scrollTo(), window.scroll() i element.scrollIntoView() bez jawnie ustawionego parametru behavior.
Gdy ustawisz scroll-behavior: smooth na elemencie html, wpływa to na każdy scroll w całym dokumencie. To kluczowa informacja, która wyjaśnia dlaczego psuje to nawigację w Next.js.
/* globals.css */
html {
scroll-behavior: smooth;
}Dlaczego to psuje nawigację w Next.js
Next.js App Router po każdej nawigacji między stronami wywołuje wewnętrznie window.scrollTo(0, 0), żeby nowa strona zaczynała się od góry. To standardowe i pożądane zachowanie – nikt nie chce wylądować na nowej podstronie w połowie treści.
Problem polega na tym, że gdy scroll-behavior: smooth jest ustawione globalnie, to scrollTo(0, 0) wykonywane przez Next.js również zostaje animowane. Zamiast natychmiastowego przeskoczenia na górę strony, przeglądarka płynnie przewija stronę do pozycji zero.
W praktyce daje to trzy zauważalne problemy:
- Widoczna animacja scrollowania – przy każdej zmianie strony użytkownik widzi, jak treść przewija się do góry. Na długich stronach trwa to nawet sekundę.
- Scroll nie dochodzi do końca – jeśli nowa strona jest krótsza od poprzedniej, przeglądarka zaczyna animację scrollowania, ale w trakcie zmienia się wysokość dokumentu. Animacja kończy się przedwcześnie i strona zatrzymuje się kilkadziesiąt pikseli od góry.
- Wyścig z renderowaniem – Next.js renderuje nową stronę i jednocześnie trwa animacja scrollowania ze starej pozycji. Oba procesy walczą o pozycję scrolla, co powoduje nienaturalne przeskakiwanie treści.
Rozwiązanie: usuń globalne scroll-behavior
Najczystsze rozwiązanie to całkowite usunięcie scroll-behavior: smooth z elementu html. Nie próbuj obchodzić problemu hackami w JavaScript, tymczasowym nadpisywaniem styli ani przechwytywaniem eventów routera. Po prostu usuń tę jedną regułę.
/* ❌ Nie rób tego w Next.js */
html {
scroll-behavior: smooth;
}/* ✅ Usuń scroll-behavior z html */
html {
/* scroll-behavior: smooth; ← usunięte */
}A co z płynnym scrollowaniem do sekcji?
Skoro usuwamy scroll-behavior: smooth z CSS, jak obsłużyć płynne przewijanie do elementu z #id? Odpowiedź: za pomocą JavaScript i metody scrollIntoView().
Zamiast polegać na globalnej regule CSS, która wpływa na każdy scroll w aplikacji, wywołaj scrollIntoView({ behavior: "smooth" }) tylko tam, gdzie faktycznie chcesz animacji. To daje pełną kontrolę – decydujesz, który scroll jest płynny, a który natychmiastowy.
function scrollToSection(id: string) {
document.getElementById(id)?.scrollIntoView({
behavior: "smooth",
block: "start",
});
}Parametr behavior: "smooth" przekazany bezpośrednio do scrollIntoView() działa niezależnie od wartości scroll-behavior w CSS. Nawet jeśli globalnie scroll jest ustawiony na auto (domyślna wartość), wywołanie z behavior: "smooth" animuje przewijanie.
Przykład: spis treści z płynnym scrollowaniem
Typowy przypadek użycia to spis treści na blogu, który po kliknięciu przewija do odpowiedniego nagłówka. Oto jak to zaimplementować w Next.js bez globalnego scroll-behavior: smooth:
"use client";
interface TocItem {
id: string;
text: string;
level: number;
}
export function TableOfContents({ items }: { items: TocItem[] }) {
const handleClick = (e: React.MouseEvent, id: string) => {
e.preventDefault();
document.getElementById(id)?.scrollIntoView({
behavior: "smooth",
block: "start",
});
// Opcjonalnie: zaktualizuj URL bez przeładowania
window.history.pushState(null, "", `#${id}`);
};
return (
<nav>
<ul>
{items.map((item) => (
<li key={item.id}>
<a
href={`#${item.id}`}
onClick={(e) => handleClick(e, item.id)}
>
{item.text}
</a>
</li>
))}
</ul>
</nav>
);
}Przykład: przycisk „wróć na górę”
Kolejny popularny przypadek – przycisk, który płynnie przewija stronę na samą górę. Zamiast polegać na scroll-behavior: smooth w CSS i window.scrollTo(0, 0), użyj scrollTo z jawnym parametrem behavior:
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: "smooth",
});
}Ta wersja scrollTo() przyjmuje obiekt z parametrem behavior, który – tak samo jak w scrollIntoView() – nadpisuje globalne ustawienie CSS. Dzięki temu scroll w tym konkretnym miejscu jest płynny, ale Next.js wewnętrznie nadal może robić natychmiastowy scrollTo(0, 0) przy nawigacji.
Porównanie obu podejść
Poniższa tabela zestawia globalne scroll-behavior: smooth z podejściem opartym o scrollIntoView() w kontekście Next.js:
| scroll-behavior: smooth (CSS) | scrollIntoView() (JS) | |
|---|---|---|
| Wpływ na nawigację Next.js | Animuje każdy scrollTo() – w tym wewnętrzny scroll routera | Nie wpływa na nawigację – router scrolluje natychmiastowo |
| Kontrola | Globalna – wszystko albo nic | Precyzyjna – tylko tam, gdzie wywołasz |
| Anchory (#id) | Automatycznie płynne dla wszystkich linków | Wymagają obsługi w JavaScript |
| Kompatybilność z frameworkami | Konfliktuje z wewnętrznym scrollem Next.js, Remix, Astro | Bezkonfliktowe – nie zmienia globalnego zachowania |
| Ilość kodu | 1 linijka CSS | Kilka linijek JS w każdym miejscu użycia |
Opcja pośrednia: scroll-behavior tylko na wybranym kontenerze
Jeśli masz sekcję strony, w której wiele elementów wymaga płynnego scrollowania (np. karuzela lub dokumentacja z wieloma anchorami), możesz ustawić scroll-behavior: smooth na konkretnym kontenerze zamiast na html:
/* Tylko wybrany kontener scrolluje płynnie */
.docs-sidebar {
overflow-y: auto;
scroll-behavior: smooth;
}To podejście nie wpływa na scroll głównego dokumentu (html), więc nawigacja Next.js działa natychmiastowo. Jednocześnie wewnątrz kontenera kliknięcia w anchory i programatyczne scrolle są płynne bez dodatkowego JavaScriptu.
Podsumowanie
html { scroll-behavior: smooth } to jedna z tych reguł CSS, które wyglądają na idealny skrót, ale w aplikacjach z client-side routingiem powodują więcej problemów niż korzyści. W Next.js App Router prowadzą do irytującego animowanego scrollowania przy każdej nawigacji, scrollu który nie dociera do góry strony i nienaturalnego przeskakiwania treści.
Rozwiązanie jest proste:
- Usuń
scroll-behavior: smoothz globalnych styli. - Tam, gdzie potrzebujesz płynnego scrollowania, użyj
scrollIntoView({ behavior: "smooth" })lubscrollTo({ top: 0, behavior: "smooth" }). - Opcjonalnie – ustaw
scroll-behavior: smoothna konkretnym kontenerze CSS, a nie nahtml.
Jedna usunięta linijka CSS. Pełna kontrola nad scrollowaniem. Zero konfliktów z routerem.
Powiązane artykuły
WCAG i strona dla szkoły: co jest naprawdę ważne poza samym hasłem „dostępność”
Czym jest WCAG, skąd wynika obowiązek dla szkół publicznych, jakie problemy rozwiązuje i dlaczego ma znaczenie dla codziennej pracy z treścią.
Strona www dla firmy budowlanej: co powinna zawierać, żeby zbierać leady
Jak powinna wyglądać skuteczna strona internetowa dla firmy budowlanej? Zakres treści, realizacje, SEO i elementy, które realnie pomagają zdobywać zapytania.
nextjs-toploader: pasek postępu w Next.js w 5 minut
Jak dodać pasek postępu ładowania strony w Next.js App Router? Konfiguracja nextjs-toploader, dostosowanie kolorów i kiedy warto go użyć.
Chcesz wdrożyć podobną stronę albo poprawić SEO?
Pracuję lokalnie w Lublin i zdalnie w całej Polsce. Jeśli ten temat dotyczy Twojej strony, poniżej masz dwa sensowne następne kroki.
Główny landing dla firm usługowych, które chcą więcej telefonów i formularzy.