Frontend Development using React.js or Angular

Chapter 7
Navigation and Application Routing

Diploma in Full Stack Programming
Frontend Development using React.js or Angular   •   Diploma

Navigation and Application Routing

Introduction

A modern frontend application is usually a collection of views rather than a single document. Users move between a dashboard, product catalogue, profile, administration area, and error pages without expecting the browser to reload the entire application. Application routing provides the rules and mechanisms that map a URL to a view, obtain parameters from that URL, control access, and support navigation through links or programmatic actions.

In a single-page application (SPA), the browser initially downloads an application shell. A routing library then interprets changes in the address bar and selects the appropriate component. React applications commonly use React Router, while Angular provides the built-in Angular Router. Although their APIs differ, both implement the same architectural ideas: route configuration, route matching, nested views, parameters, navigation, guards, lazy loading, and fallback handling.

Routing is therefore more than a convenience feature. It is part of the application’s information architecture, security boundary, usability model, and performance strategy.

Learning Objectives

After completing this chapter, the learner should be able to:

  • explain client-side routing and its relationship with browser history;
  • define static, dynamic, nested, and wildcard routes;
  • implement navigation with React Router or Angular Router;
  • read route parameters and query parameters safely;
  • design nested views and layout routes;
  • apply route guards and authentication-aware navigation;
  • configure lazy loading and explain its performance benefits;
  • create useful not-found and error pages;
  • identify routing security, accessibility, testing, and maintenance concerns.

1. The Routing Model

A route is a rule that associates a URL pattern with a view or component. For example:

URL patternMeaningView
/Application homeHome component
/productsProduct collectionProduct-list component
/products/:idOne productProduct-detail component
/account/settingsNested account viewSettings component
or *No route matchedNot-found component

A URL generally contains several useful parts:

  • Origin: protocol and host, such as https://example.edu.
  • Path: hierarchical resource location, such as /products/42.
  • Path parameter: variable embedded in the path, such as 42.
  • Query string: optional key-value data after ?, such as ?sort=price&page=2.
  • Fragment: text after #, often used for a page anchor.

A route is not automatically a security mechanism. Hiding a link or preventing a component from rendering does not protect data. Authorization must also be enforced by a trusted backend for every protected operation.

Client-side and server-side routing

Traditional multi-page applications request a new HTML document for every navigation. In an SPA, the router changes the displayed component and often uses the History API to change the URL without a full reload. The browser’s Back and Forward buttons remain meaningful because the router participates in the history stack.

A production server must still be configured correctly. If a user directly requests /products/42, the server must return the SPA entry document, unless the application uses server-side routing for that path. Otherwise, refreshing a valid client route can produce a server-level 404.

Route matching

Routers typically compare the current path with route definitions. Specific routes should be designed carefully because matching rules vary by framework and version. A wildcard route should be placed or configured as a final fallback, not before valid routes. Ambiguous patterns make applications difficult to test and maintain.

2. Basic Route Configuration

React Router

A current React Router application may use a data-router configuration:

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Home from "./Home";
import Products from "./Products";
import NotFound from "./NotFound";

const router = createBrowserRouter([
  { path: "/", element: <Home /> },
  { path: "/products", element: <Products /> },
  { path: "*", element: <NotFound /> }
]);

export default function App() {
  return <RouterProvider router={router} />;
}

A route may also be declared with JSX using createBrowserRouter alternatives, depending on the project version. The important principle is that the router owns route selection and the rendered components remain focused on presentation and application behaviour.

Navigation should normally use Link or NavLink, not ordinary anchors for internal routes:

import { NavLink } from "react-router-dom";

export default function Navigation() {
  return (
    <nav aria-label="Primary navigation">
      <NavLink to="/">Home</NavLink>
      <NavLink to="/products">Products</NavLink>
    </nav>
  );
}

NavLink can indicate the active route, which improves orientation and accessibility when the active state is exposed visually and programmatically.

Angular Router

Angular route definitions are commonly placed in a routing module or supplied through provideRouter:

import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { ProductsComponent } from './products.component';
import { NotFoundComponent } from './not-found.component';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'products', component: ProductsComponent },
  { path: '**', component: NotFoundComponent }
];

The template uses routerLink and router-outlet:

<nav aria-label="Primary navigation">
  <a routerLink="/">Home</a>
  <a routerLink="/products">Products</a>
</nav>
<router-outlet></router-outlet>

The empty path represents the application root. The wildcard route ** should be last because it matches any otherwise unmatched URL.

3. Nested Views and Layouts

Nested routing represents a containment relationship. A parent route supplies a shared layout, while a child route supplies the changing content. Typical examples include an account layout with profile, orders, and settings children.

Conceptually:

/account
 ├── /profile
 ├── /orders
 └── /settings

In React Router:

const routes = [
  {
    path: "/account",
    element: <AccountLayout />,
    children: [
      { index: true, element: <Profile /> },
      { path: "orders", element: <Orders /> },
      { path: "settings", element: <Settings /> }
    ]
  }
];

The layout must render an Outlet:

import { Outlet } from "react-router-dom";

function AccountLayout() {
  return (
    <section>
      <h1>Account</h1>
      <AccountMenu />
      <Outlet />
    </section>
  );
}

In Angular, children are declared with children and rendered through a nested outlet:

{
  path: 'account',
  component: AccountComponent,
  children: [
    { path: '', component: ProfileComponent },
    { path: 'orders', component: OrdersComponent },
    { path: 'settings', component: SettingsComponent }
  ]
}

The AccountComponent template includes <router-outlet></router-outlet>. Relative links are often preferable inside a feature area because they remain understandable if the feature is moved under another parent path.

4. Parameters, Query Parameters, and State

A path parameter identifies a resource as part of the URL structure. A query parameter usually modifies a collection or view, such as filtering, sorting, or pagination.

For /products/42?sort=price, 42 is a path parameter and sort=price is a query parameter. Parameters should be validated before being used. A value read from the URL is untrusted input and may be missing, malformed, excessively large, or intentionally malicious.

React example

import { useParams, useSearchParams } from "react-router-dom";

function ProductDetail() {
  const { id } = useParams();
  const [searchParams] = useSearchParams();
  const view = searchParams.get("view") ?? "summary";

  if (!/^\d+$/.test(id ?? "")) {
    return <p>Invalid product identifier.</p>;
  }

  return <ProductScreen productId={id} view={view} />;
}

The component should then request the product from an API and handle loading, success, empty, and failure states. It should not assume that a syntactically valid identifier guarantees that a resource exists.

Angular example

import { ActivatedRoute } from '@angular/router';

export class ProductComponent {
  productId = '';
  view = 'summary';

  constructor(route: ActivatedRoute) {
    route.paramMap.subscribe(params => {
      this.productId = params.get('id') ?? '';
    });
    route.queryParamMap.subscribe(params => {
      this.view = params.get('view') ?? 'summary';
    });
  }
}

Angular applications may use route resolvers to obtain data before activation. Resolvers can improve consistency but should not make the interface appear frozen; loading and failure behaviour must still be designed.

URL state versus application state

URL state is shareable, bookmarkable, and recoverable after refresh. It is suitable for selected filters, search terms, tabs, and pagination. Temporary UI state, such as whether a tooltip is open, usually belongs in component state. Sensitive information should not be placed in URLs because URLs may appear in browser history, server logs, analytics systems, screenshots, and referrer data.

5. Navigation Strategies

Navigation can be declarative or programmatic.

  • Declarative navigation uses links and is generally best for user-selected destinations.
  • Programmatic navigation is appropriate after form submission, authentication, deletion, or a workflow transition.

React example:

import { useNavigate } from "react-router-dom";

function SaveButton() {
  const navigate = useNavigate();

  async function save() {
    await saveRecord();
    navigate('/records');
  }

  return <button onClick={save}>Save</button>;
}

Angular example:

constructor(private router: Router) {}

save(): void {
  this.service.save().subscribe(() => {
    this.router.navigate(['/records']);
  });
}

Navigation should preserve user intent. For example, after login, an application may return the user to the originally requested route. The return URL must be validated against allowed internal paths; blindly redirecting to arbitrary user-supplied URLs can create an open-redirect vulnerability.

Use meaningful document titles, focus management, and announced status messages where route changes are not automatically obvious to assistive technology. A route transition should not leave keyboard users focused on an element that no longer exists.

6. Authentication and Route Guards

Authentication answers “Who is the user?” Authorization answers “What may this user do?” A route guard can improve the client experience by preventing unauthenticated users from entering a protected view, but it cannot replace server-side authorization.

A common policy is:

  1. determine whether a valid session exists;
  2. allow public routes without authentication;
  3. redirect unauthenticated users to login;
  4. preserve a safe return path;
  5. after authentication, re-evaluate the requested route;
  6. enforce permissions on the backend as well.

React wrapper example:

import { Navigate, Outlet, useLocation } from "react-router-dom";

function RequireAuth() {
  const user = useCurrentUser();
  const location = useLocation();

  if (user === undefined) return <Loading />;
  if (!user) {
    return <Navigate to="/login" replace state={{ from: location }} />;
  }
  return <Outlet />;
}

Angular guard example:

import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = (_route, state) => {
  const auth = inject(AuthService);
  const router = inject(Router);

  return auth.isAuthenticated()
    ? true
    : router.createUrlTree(['/login'], {
        queryParams: { returnUrl: state.url }
      });
};

Guards should not expose sensitive data while checking authentication. Applications should avoid storing long-lived credentials in easily accessible browser storage when a safer session design is available. Cookies used for sessions should be considered with HttpOnly, Secure, and appropriate SameSite settings, while cross-site request forgery protections must be designed at the server boundary.

7. Lazy Loading and Performance

Lazy loading divides the application into chunks that are downloaded when needed. It is valuable when an application contains large or infrequently used features, such as administration or reporting. It reduces initial transfer and parsing work, but it introduces additional requests and a possible delay when a user first enters the feature.

React example:

import { lazy, Suspense } from 'react';

const Reports = lazy(() => import('./Reports'));

function ReportsRoute() {
  return (
    <Suspense fallback={<p>Loading reports…</p>}>
      <Reports />
    </Suspense>
  );
}

Angular commonly lazy-loads a route:

{
  path: 'admin',
  loadChildren: () =>
    import('./admin/admin.routes').then(m => m.ADMIN_ROUTES)
}

Lazy loading is not automatically beneficial for every component. Excessive fragmentation can increase request overhead and complicate error handling. Teams should measure initial load, route transition latency, chunk size, caching, and failure rates. Prefetching may improve later navigation, but it consumes bandwidth and should respect constrained devices and networks.

8. Error Pages and Failure Handling

A professional router distinguishes among several failures:

  • 404 Not Found: no route matches the requested path.
  • 403 Forbidden: the user is authenticated but lacks permission.
  • 401 Unauthorized: authentication is required or has expired.
  • Route loading failure: a lazy chunk cannot be downloaded.
  • Data failure: the route exists, but its API request fails.

These states should not all be represented by a generic “Something went wrong” message. Error pages should explain the situation, provide a useful next action, preserve accessibility, and avoid disclosing stack traces or internal data.

React Router supports route-level error elements in data-router configurations. Angular applications can use an error component, navigation error events, HTTP interceptors, and feature-level error states. A global error boundary or error handler should log diagnostic information to an approved monitoring system while presenting a safe message to the user.

9. Applied Case: Campus Learning Portal

Background

This illustrative case concerns a campus learning portal with public course information and protected student functions. The portal includes /courses, /courses/:courseId, /student, /student/grades, and /admin.

Problem

The initial implementation placed all screens in one component. Links caused inconsistent browser history, refreshing /student/grades produced a server 404, and users could see an administration link even when they lacked permission. The initial JavaScript bundle was also unnecessarily large.

Analysis

The team identified three route groups: public catalogue routes, authenticated student routes, and role-protected administration routes. The student section required a shared layout and child views. Course identifiers had to be validated and course data had to be fetched from the API. The server needed a fallback configuration for client-side paths.

Approach and solution

The team implemented:

  1. public routes for home, course list, and course detail;
  2. an authenticated parent route for /student with nested children;
  3. a role guard for /admin;
  4. a not-found page and a route-level error view;
  5. lazy loading for the administration feature;
  6. links using router-aware components rather than raw internal anchors;
  7. safe return navigation after login;
  8. server configuration that returned the SPA entry document for known client routes;
  9. backend authorization on grades and administrative APIs;
  10. automated tests for route matching, guards, parameters, and refresh behaviour.

Outcome

Navigation became predictable, deep links worked after refresh, and the administration code was excluded from the initial bundle. Unauthorized users were redirected without receiving administrative data. The team also improved keyboard focus and active navigation indicators.

Learning points

Routing should be designed as an application structure, not added screen by screen. Layout routes reduce duplication, URL parameters make views shareable, guards improve user flow, and lazy loading should be guided by measurement. Most importantly, client routing and server authorization solve different problems and must be implemented together.

10. Testing and Professional Practice

Routing tests should cover both normal and abnormal paths. Important cases include:

  • each public route renders the intended view;
  • dynamic parameters are accepted only in valid forms;
  • missing records produce an appropriate result;
  • protected routes redirect unauthenticated users;
  • role restrictions distinguish authentication from authorization;
  • login returns users only to safe internal destinations;
  • browser Back and Forward behaviour is correct;
  • direct refreshes work in the deployment environment;
  • lazy-loading failures have a recovery path;
  • links are keyboard accessible and have meaningful names.

Use integration or end-to-end tests for complete navigation flows, and unit tests for guards, parameter parsing, and route utilities. Avoid testing only implementation details such as private component variables; test observable URL, view, and navigation outcomes.

11. Limitations, Governance, and Contemporary Practice

Client-side routing can increase complexity because it combines browser history, asynchronous data, access control, code splitting, and deployment configuration. Search-engine visibility may require server-side rendering or pre-rendering for public content. Offline applications must decide how cached routes and stale data are handled. Analytics collection should respect consent and avoid placing personal or sensitive values in URLs.

Current frontend practice increasingly combines routing with data loaders, server rendering, streaming, typed route definitions, and framework-level caching. These techniques can improve performance and consistency, but they do not eliminate the need for clear route design, secure APIs, accessible navigation, and comprehensive tests. A framework feature should be adopted because it solves a measured problem, not merely because it is fashionable.

Best-Practice Checklist

  • Keep route names stable, meaningful, and predictable.
  • Prefer router links for internal navigation.
  • Use nested layouts for shared structure.
  • Validate all route and query input.
  • Treat guards as user-flow controls, not security boundaries.
  • Enforce authorization on the backend.
  • Provide explicit loading, empty, forbidden, not-found, and failure states.
  • Lazy-load large or infrequently used features and measure the result.
  • Configure the production server for deep links.
  • Protect sensitive information from URLs, logs, and analytics.
  • Test direct navigation, refresh, history, accessibility, and failures.

Chapter Summary

Application routing maps URLs to frontend views and gives an SPA a coherent navigational structure. React Router and Angular Router provide comparable capabilities through different APIs. Static routes identify fixed views, dynamic parameters identify resources, query parameters represent view state, and nested routes support reusable layouts. Declarative links are normally preferable for user navigation, while programmatic navigation supports workflow transitions.

Authentication-aware routing uses guards or protected wrappers to guide users, but real authorization must be enforced by the server. Lazy loading can reduce initial application cost, whereas wildcard routes and explicit error handling improve resilience. Professional routing also requires deployment support for deep links, accessible navigation, parameter validation, secure redirect handling, and automated tests. Good route design connects usability, performance, maintainability, and security.

Key Terms

Route: A rule mapping a URL pattern to a view or component.

Router: Software that matches URLs and coordinates navigation.

Single-page application: An application that updates views without normally loading a new document for each route.

Client-side routing: URL and view management performed in the browser.

Path parameter: A variable embedded in a URL path, such as :id.

Query parameter: Optional key-value data following ? in a URL.

Nested route: A route rendered within a parent route’s layout.

Layout route: A parent route providing shared structure around child views.

Outlet: A designated location where a matched child route is rendered.

Declarative navigation: Navigation expressed through links or route declarations.

Programmatic navigation: Navigation initiated by application code.

Route guard: Logic that allows, denies, or redirects route activation.

Authentication: Verification of a user’s identity.

Authorization: Determination of what an identified user may do.

Lazy loading: Loading a feature only when it is needed.

Wildcard route: A fallback route matching otherwise unmatched paths.

Deep link: A URL that directly opens a nested application view.

Open redirect: An unsafe redirect allowing navigation to an attacker-controlled destination.

Route resolver: Logic that obtains data before a route is activated.

History API: Browser API used to manipulate URL history without a full document reload.

Review Questions

Short Answer Questions

  1. What is the difference between client-side routing and traditional server-side page navigation?
  2. Distinguish a path parameter from a query parameter.
  3. Why is a wildcard route normally configured as a fallback?
  4. What is the purpose of an outlet in nested routing?
  5. Why can a route guard not replace backend authorization?

Long Answer Questions

  1. Explain how nested routes support reusable application layouts. Compare the role of React Router’s Outlet with Angular’s router-outlet.
  2. Describe a secure authentication-aware navigation flow, including login redirection and backend authorization.
  3. Explain lazy loading, its benefits, and its possible disadvantages.
  4. Discuss the deployment requirements for reliable deep links in a single-page application.
  5. Design an error-handling strategy that distinguishes not-found, forbidden, data, and lazy-loading failures.

Analytical / Application Questions

  1. Design a route hierarchy for an online store containing public product pages, an authenticated customer account, and a role-protected staff area. Explain where layouts, guards, and lazy loading should be used.
  2. A user requests /reports/abc?format=pdf, but report identifiers must be numeric and only managers may export reports. Describe the validation, authorization, error, and navigation decisions required.
  3. A portal works when links are clicked but returns a server 404 after refreshing a nested route. Diagnose the cause and propose frontend, server, and testing remedies.

Multiple Choice Questions with Answers

Question 1

Which mechanism is normally used for internal navigation in a React Router application?

A. The Link component

B. A database trigger

C. An SQL redirect

D. A CSS media query

Correct Answer: A. The Link component

Question 2

In /products/42?sort=price, what is 42?

A. A fragment

B. A path parameter value

C. A route guard

D. A component outlet

Correct Answer: B. A path parameter value

Question 3

What is the primary purpose of a nested route?

A. To disable browser history

B. To render child content within a parent layout

C. To encrypt query parameters

D. To replace backend authorization

Correct Answer: B. To render child content within a parent layout

Question 4

Which Angular route pattern is commonly used as a final fallback for unmatched paths?

A. path: ''

B. path: 'home'

C. path: '**'

D. path: ':id'

Correct Answer: C. path: ''**

Question 5

What is a major benefit of lazy loading?

A. It guarantees database security

B. It can reduce the initial JavaScript download

C. It removes the need for error pages

D. It prevents all API failures

Correct Answer: B. It can reduce the initial JavaScript download

Question 6

Which statement about route guards is correct?

A. They make backend authorization unnecessary

B. They are useful for controlling client navigation flow

C. They validate every database query automatically

D. They encrypt browser history

Correct Answer: B. They are useful for controlling client navigation flow

Question 7

Why should sensitive information generally not be placed in query strings?

A. Query strings cannot contain text

B. They may appear in history, logs, or analytics

C. Routers cannot read them

D. They always disable lazy loading

Correct Answer: B. They may appear in history, logs, or analytics

Question 8

Which response best represents a user who is authenticated but lacks permission?

A. 200 OK

B. 301 Moved Permanently

C. 403 Forbidden

D. 304 Not Modified

Correct Answer: C. 403 Forbidden

Question 9

What deployment problem causes a valid nested SPA route to return a server 404 after refresh?

A. The server is not configured to serve the SPA entry document for that path

B. The browser has too many bookmarks

C. The component has no CSS class

D. The query string is empty

Correct Answer: A. The server is not configured to serve the SPA entry document for that path

Question 10

Which practice improves accessibility during route navigation?

A. Using unnamed links everywhere

B. Removing focus after every navigation

C. Providing meaningful link names and managing focus appropriately

D. Hiding the active route from assistive technology

Correct Answer: C. Providing meaningful link names and managing focus appropriately

Frontend Development using React.js or Angular
Chapter 7 of 14