Course outline

Next.js / Basic: routes and execution boundaries / Lesson 4

Navigation, URL state and safe links

Represent shareable state in the URL and preserve safe navigation semantics across links, hooks and prefetch.

About 4 min readContent reviewed
On this page

Lesson goals

  • Distinguish pathname, dynamic params and search parameters.
  • Validate navigation targets and keep rendering free of side effects.

Choose URL state for reproducible views

A course filter, sort order and page cursor often belong in the URL so reloads and shared links describe the same intended view. Temporary focus or an open dropdown can remain local state. Dynamic path values identify route segments, while search parameters carry query input and may be repeated or absent. Validate both before source access. A URL is not a durable snapshot of a changing dataset; a shared cursor still needs scope and freshness rules. Keep derived UI synchronized with the authoritative URL rather than maintaining competing copies that disagree after back/forward navigation.

The fragment links to a known internal course route and supplies a query value through URLSearchParams. Encoding keeps separators inside the value rather than changing query structure. It does not make an arbitrary destination safe or authorize the target data. Link can prefetch route work depending on mode and runtime behavior, so the server must not mutate state merely because a route is rendered. Keep assumptions about partial prefetching separate from this course’s primary configuration. No navigation, prefetch request or browser history operation was executed for the snippet.

tsx Illustrative encoded internal link
import Link from "next/link";
const query=new URLSearchParams({view:"summary"}).toString();
export function CourseLink() {
  return <Link href={`/courses/server-components?${query}`}>Open course</Link>;
}

The destination is a fixed internal path with view=summary. JSX and Next Link were not rendered or clicked.

Use App Router navigation APIs in the right boundary

Client navigation hooks come from next/navigation under the App Router, not the older Pages Router package. usePathname and useSearchParams expose different URL dimensions and should not be treated as a complete authorization context. A page’s asynchronous searchParams prop can serve server-side filtering, while a client control can update the URL through the router or links. Decide when push should add history and replace should update the current entry. Avoid calling router navigation repeatedly during render. Preserve accessible links and meaningful pending states rather than making every navigation an opaque click handler.

Reject open-redirect and script-like targets

The local function accepts only a relative path beginning with /courses/ and rejects a protocol-relative target or script scheme. This deliberately small policy avoids interpreting any caller-provided URL as a trusted navigation target. It is not a full URL sanitizer or a framework security test; richer designs need exact parsed-origin and path rules. The known prefix is a synthetic application choice. Never pass untrusted strings to router.push or redirect merely because they arrived as a next query parameter. Validate the target under a server/client policy appropriate to the action.

javascript Complete offline navigation-target rule
function safeCourseTarget(value) {
  return typeof value === "string" && value.startsWith("/courses/") && !value.startsWith("//");
}
console.assert(safeCourseTarget("/courses/server-components"));
console.assert(!safeCourseTarget("//external.invalid/courses/x"));
console.assert(!safeCourseTarget("javascript:alert(1)"));

Only the declared relative course target passes. The function performs no navigation and does not claim complete URL canonicalization.

Review refresh, preservation and identity changes

router.refresh requests a fresh server-component result for the current route and merges it while preserving relevant client state; it is not by itself a universal invalidation of every persistent cache. A stale cached source can therefore yield the same data again. Back/forward behavior and preserved layouts depend on the selected mode, so test the real lifecycle instead of assuming a remount. After login or logout, retire old-principal client state and pending results as required by the privacy contract. This course runs only the target predicate and does not test navigation hooks, router cache, prefetch or browser behavior.

Common errors and fixes

A filter disappears on reload

Why it happens
Shareable view state lived only in local memory.
How to fix it
Represent intended filters in validated URL state.
Prevent it next time
Test reload, shared link and back/forward.

A next parameter redirects to an external site

Why it happens
Caller input was trusted as a destination.
How to fix it
Apply an explicit approved-target policy.
Prevent it next time
Test protocol-relative and script-like values.

router.refresh returns the same stale record

Why it happens
A UI refresh was mistaken for source-cache invalidation.
How to fix it
Inspect and invalidate the relevant server data contract.
Prevent it next time
Test cache state and route refresh separately.

Check your understanding

1. Does encoding a query value make the linked operation authorized?

Show worked solution

No. Encoding preserves URL structure. The receiving route must still validate vocabulary, resolve the current principal and enforce object policy. A well-formed query can request forbidden data, and a fixed internal path can still lead to a protected operation. Keep syntax and authority separate.

2. Why can prefetching make render-time side effects unsafe?

Show worked solution

The framework may render or fetch a destination before the user commits to navigating. A render-time write could therefore happen on hover or visibility rather than an authenticated explicit mutation. Keep rendering side-effect free and use a separate validated action with replay handling for intended changes.

Key takeaways

  • URL state needs validation and deliberate history semantics.
  • Navigation refresh and persistent cache invalidation are distinct operations.

Official references