Accessibility (a11y): next-route-announcer only announces title changes, H1/path fallbacks never used
Link to the code that reproduces this issue
https://github.com/Unit2795/next-announcer-issue
To Reproduce
- Ensure you have a screen reader and node.js installed.
- Clone the repository.
- Install dependencies using
npm install. - Run the development server using
npm run dev. - Start your screen reader
- Open the application in a web browser, you should see the homepage with a few link buttons.
- Click on the "Test" link to navigate to the
/testpage - Observe that the screen reader announces the navigation change.
- Click on the "Test with Subpath" link to navigate to the
/test/subpathpage. - Observe that the screen reader does not announce the navigation change.
Current vs. Expected behavior
Current:
- If there is a document title, check if it changed. If it didn't change, do not announce a change.
- Fallback to
<h1>only occurs if there is no page title at all. (IE: document.title is undefined, null, or falsy.) - Since pages usually have a title, the H1 fallback path is effectively a no-op.
- The path is never announced.
Expected:
- If document title changed, announcer reads new title. If unchanged, proceed to next step:
- If h1 changed, announcer reads new h1. If unchanged, proceed to next step:
- If URL path changed, announcer reads URL path
Provide environment information
Operating System:
Platform: win32
Arch: x64
Version: Windows 11 Pro
Available memory (MB): 65450
Available CPU cores: 24
Binaries:
Node: 25.2.1
npm: 11.6.2
Yarn: N/A
pnpm: N/A
Relevant Packages:
next: 16.1.0-canary.4 // Latest available version is detected (16.1.0-canary.4).
eslint-config-next: N/A
react: 19.2.0
react-dom: 19.2.0
typescript: 5.9.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Linking and Navigating
Which stage(s) are affected? (Select all that apply)
next dev (local), next build (local), next start (local), Vercel (Deployed), Other (Deployed)
Additional context
The next-route-announcer accessibility live region will generally only announce route changes when the document.title changes. It will not announce the title change only when there is no title at all (document.title is undefined, null, or falsy), but most sites generally have a title. So the logic for announcing based on an h1 or path is essentially a no op.
https://github.com/vercel/next.js/blob/737ddf775b491fb123fde41cc21a6a2d7ad599b9/packages/next/src/client/components/app-router-announcer.tsx#L48-L66
Based on the documentation (Next.js - Architecture: Accessibility), I would anticipate that the behavior should be cascading like I described in my expected behavior. What's more, the documentation for the app router mentions that the final fallback is to announce the path, but no such logic to announce the path exists for the app-router-announcer.
Sites should provide a unique title per page, according to WCAG SC 2.4.2. However, there could plausibly be minor state variations in the path. (IE: /product?sort=price vs /product?sort=rating OR /order/dinein vs /order/carryout). In this case, when the document title does not change, no announcement will be made by the next-route-announcer. This is not accessible; because a navigation has still occurred, UI may have changed slightly, focus may have shifted, and the user needs to be made aware of that.
Fix: Route Announcer Cascade Logic
Problem
The AppRouterAnnouncer component only announced route changes when document.title changed. This meant that:
- When navigating between subpaths with the same title (e.g.,
/test→/test/subpath), no announcement was made - The H1 fallback only worked if there was no title at all (which is rare in practice)
- The path fallback was never used, even though it was documented as the final fallback
This created an accessibility issue where screen reader users weren't notified of route changes when only the path or H1 changed.
Solution
Implemented a cascade fallback logic that checks for changes in priority order:
-
Title - If
document.titlechanged → announce the new title - H1 - If title didn't change but H1 changed → announce the new H1
- Path - If neither title nor H1 changed but path changed → announce the path
Changes Made
packages/next/src/client/components/app-router-announcer.tsx
- Added import for
extractPathFromFlightRouterStateto get current path from router tree - Added refs to track previous values:
-
previousH1- tracks previous H1 content -
previousPath- tracks previous path
-
- Implemented cascade logic in
useEffect:- Checks title change first
- Falls back to H1 if title unchanged
- Falls back to path if both title and H1 unchanged
- Updated comments to reflect new cascade behavior
test/unit/app-router-announcer.test.tsx (new file)
Created comprehensive unit tests covering:
- First load (no announcement)
- Title change announcement
- H1 change announcement when title unchanged
- Path change announcement when title and H1 unchanged
- Priority: title over H1, H1 over path
- Fallback scenarios (empty title, empty H1)
- No announcement when nothing changes
How to Verify
Running Tests
npm test -- test/unit/app-router-announcer.test.tsx
All 9 tests should pass:
- ✓ should not announce on first load
- ✓ should announce when title changes
- ✓ should announce H1 when title does not change but H1 changes
- ✓ should announce path when title and H1 do not change but path changes
- ✓ should prioritize title over H1 when both change
- ✓ should prioritize H1 over path when title does not change
- ✓ should handle empty title and use H1 fallback
- ✓ should handle empty title and H1, use path fallback
- ✓ should not announce when nothing changes
Manual Testing
-
Test with screen reader:
- Start a Next.js app with App Router
- Navigate between routes with the same title but different paths
- Verify that screen reader announces the route change (using H1 or path)
-
Test cascade logic:
- Navigate to a route with title "Test Page" and H1 "Test"
- Navigate to
/test/subpathwith same title but different H1 "Subpath" - Screen reader should announce "Subpath" (H1 fallback)
- Navigate to another route with same title and H1 but different path
- Screen reader should announce the path
-
Test priority:
- Navigate between routes where both title and H1 change
- Verify that title is announced (not H1)
- Navigate where title stays same but H1 and path change
- Verify that H1 is announced (not path)
Example Test Case
// Before: No announcement when navigating /test → /test/subpath
// (if title stays the same)
// After: Announces "Subpath" (H1) or "/test/subpath" (path)
// depending on what changed
Related Issue
Fixes #86660
Testing Environment
- Jest with jsdom environment
- React Testing Library
- Shadow DOM support for announcer element
@rosbitskyy Thank you for working on this fix, it looks great.
I have a few thoughts on the PR I saw:
- There appears to be another route announcer used in the pages router. I think this would need to be updated as it also does not implement the cascading logic. https://github.com/vercel/next.js/blob/canary/packages/next/src/client/route-announcer.tsx
- In your code, you check if the title, h1, and path all were undefined in order to not announce on first load. But only checking if the path was undefined should be enough?https://github.com/rosbitskyy/next.js/blob/6a88ec24ed4d5b511b839d925c3ed372f4a782bd/packages/next/src/client/components/app-router-announcer.tsx#L61-L63
More general questions about Next.js path change detection behavior:
- Does the URL path checking logic account for changes in query parameters? This is a tough one because query parameter changes could correlate with a navigation OR they are embedded by the current page to store data without navigation. Almost anything in the path could change but not actually correlate with navigation
- Related to the above point, does Next announce changes if a URL fragment (same-page link) is used?
General Thoughts
I know in my original message I mention that I expected cascading logic, just based on the language in the Next.js docs. I'm not sure if cascading logic for the route announcer is actually the best path forward though. Maybe a more thorough review on the navigation accessibility logic for Next.js might be warranted. Simply announcing based on path changes might result in announcements when it's not desired.
It might be worth allowing <Link> elements and useRouter to customize the behavior of the announcer. Like if links could turn off the announcer or override the message it would announce. Similarly, pages/layouts may wish to override the message announced based on dynamic data.
the PR I saw
fyi no PR has been filed in the next.js repo - the linked PR was opened (and merged) in a personal fork only.