Observed behaviour
Clicking the browser back button on the dashboard page (e.g. /projects/dutch_news_media/dashboard?tab=summary) appeared to get stuck in an infinite refresh/redirect loop. Could not be reproduced — may have been a one-time fluke.
Diagnosis
The likely root cause is this useEffect in frontend/src/routes/projects.$project.dashboard.tsx:
useEffect(() => {
setQueryState(serializeQuery(query));
}, [query, setQueryState]);
This effect fires on every mount, including when the user arrives via back-navigation. setQueryState from nuqs uses history.push by default, meaning it adds a new browser history entry.
The risk: if serializeQuery(deserializeQuery(savedQueryString)) produces a different compressed string than what is already in the URL (lz-string compression is not guaranteed to produce identical output when round-tripping), nuqs sees a changed value and pushes a new history entry. The result:
- User clicks back → dashboard mounts with
?query=ABC
- Effect fires → re-serialises → produces
?query=XYZ → pushes new history entry
- User clicks back → lands on
?query=ABC again → repeat
Decision
Not fixing for now, since the issue could not be reproduced. If it recurs, the fix is to skip the setQueryState call on the initial mount (only push when the query changes after mount):
const isMounted = useRef(false);
useEffect(() => {
if (!isMounted.current) { isMounted.current = true; return; }
setQueryState(serializeQuery(query));
}, [query, setQueryState]);
Observed behaviour
Clicking the browser back button on the dashboard page (e.g.
/projects/dutch_news_media/dashboard?tab=summary) appeared to get stuck in an infinite refresh/redirect loop. Could not be reproduced — may have been a one-time fluke.Diagnosis
The likely root cause is this
useEffectinfrontend/src/routes/projects.$project.dashboard.tsx:This effect fires on every mount, including when the user arrives via back-navigation.
setQueryStatefrom nuqs useshistory.pushby default, meaning it adds a new browser history entry.The risk: if
serializeQuery(deserializeQuery(savedQueryString))produces a different compressed string than what is already in the URL (lz-string compression is not guaranteed to produce identical output when round-tripping), nuqs sees a changed value and pushes a new history entry. The result:?query=ABC?query=XYZ→ pushes new history entry?query=ABCagain → repeatDecision
Not fixing for now, since the issue could not be reproduced. If it recurs, the fix is to skip the
setQueryStatecall on the initial mount (only push when the query changes after mount):