Trong trang thường, mỗi lần điều hướng là một lần tải trang mới — screen reader tự đọc lại tiêu đề.
Trong SPA không có sự kiện đó: URL đổi, DOM đổi, nhưng focus vẫn nằm ở link vừa bấm (hoặc rơi về <body>), và người dùng không được báo gì.
// sau mỗi lần đổi route
function PageTitle({ children }: { children: string }) {
const ref = useRef<HTMLHeadingElement>(null)
const pathname = usePathname()
useEffect(() => {
ref.current?.focus()
}, [pathname])
return <h1 ref={ref} tabIndex={-1}>{children}</h1>
}Ba việc cần làm khi chuyển route:
1. Đưa focus tới <h1> của trang mới (element đó cần tabindex="-1").
2. Cập nhật document.title — nhiều screen reader đọc nó.
3. Nếu chuyển trang mất thời gian, báo trạng thái qua vùng aria-live.
Lưu ý: đừng focus vào <div> bọc ngoài thay vì heading — screen reader đọc "group" rồi im, người dùng không biết đã tới trang nào.
Lưu ý Next.js App Router: router có xử lý một phần việc này, nhưng đừng mặc định là đủ — luôn kiểm tra bằng screen reader thật với các luồng có modal hoặc route lồng nhau.