Skip to the content.
Copilot Instructions Available Download this instruction file to enhance AI agent assistance for Links patterns in your codebase.
Download
links

Links and Navigation

Key Insight

Links are the fundamental building blocks of the web—navigation between resources that transforms isolated documents into an interconnected information network. In server-rendered applications, links determine how users navigate (client-side vs full page reload), what gets prefetched (performance optimization), how SEO crawlers discover content (sitemap generation, canonical URLs), and how state persists (URL parameters vs client state). The critical distinction: anchor tags (<a>) trigger full page reloads (server fetches new HTML, browser resets state), while client-side routing (<Link> components in React Router, Next.js) intercepts clicks and updates DOM without reloading (preserves state, faster navigation, enables transitions). Modern frameworks blur this line with progressive enhancement: links work as regular anchors (crawlable, no-JS fallback), enhanced with JavaScript for SPA-like navigation. Links also encode application state via URL parameters, enabling shareable state (copy URL → share exact app state), back/forward navigation (browser history), and deep linking (link directly to nested views).

Detailed Description

Internal Links (same-origin navigation):

<!-- Traditional anchor (full page reload) -->
<a href="/about">About Us</a>

<!-- React Router Link (client-side navigation) -->
<Link to="/about">About Us</Link>

<!-- Next.js Link (prefetched, client-side navigation) -->
<Link href="/about">About Us</Link>

<!-- Vue Router link -->
<router-link to="/about">About Us</router-link>

External Links (different origin):

<!-- Opens in new tab, security best practice -->
<a href="https://external.com" target="_blank" rel="noopener noreferrer">
  External Site
</a>

<!-- rel="noopener" prevents window.opener access (security)
     rel="noreferrer" prevents Referer header leakage (privacy) -->

Download Links:

<!-- Forces download instead of navigation -->
<a href="/documents/report.pdf" download="annual-report.pdf">
  Download Report
</a>

Email/Phone Links (protocol handlers):

<!-- Opens email client -->
<a href="mailto:contact@example.com?subject=Inquiry">Email Us</a>

<!-- Opens phone dialer (mobile) -->
<a href="tel:+1234567890">Call Us</a>

<!-- SMS link -->
<a href="sms:+1234567890?body=Hello">Text Us</a>

Hash Links (same-page navigation):

<!-- Scrolls to element with id="features" -->
<a href="#features">Jump to Features</a>

<!-- Smooth scroll with CSS -->
<style>
  html { scroll-behavior: smooth; }
</style>

2. Client-Side Routing vs Server Navigation

Full Page Reload (traditional anchor):

User clicks: <a href="/about">
↓
Browser sends: GET /about HTTP/1.1
↓
Server responds: HTML document
↓
Browser:
  1. Destroys current page (all state lost)
  2. Parses new HTML
  3. Downloads CSS/JS
  4. Executes scripts
  5. Renders new page
↓
Result: Slow (network + parse + execute), state lost, flash of white

Client-Side Navigation (SPA routing):

User clicks: <Link to="/about">
↓
Router intercepts click (preventDefault)
↓
Router changes URL (history.pushState('/about'))
↓
React/Vue renders new component tree
↓
Result: Fast (no network request), state preserved, smooth transition

Hybrid (Progressive Enhancement):

<!-- Next.js Link -->
<Link href="/about">
  <a>About Us</a>
</Link>

<!-- Without JavaScript: -->
User clicks → <a href="/about"> → Full page reload → Works ✓

<!-- With JavaScript: -->
User clicks → Router intercepts → Client-side navigation → Fast ✓

<!-- Best of both worlds: works without JS, enhanced with JS -->

Prefetching Strategies:

  1. On Hover (React Router, Next.js default):
    // Next.js prefetches on hover
    <Link href="/products" prefetch={true}>
      Products
    </Link>
       
    // Browser prefetches /products page when user hovers link
    // Click is instant (already loaded)
    
  2. On Viewport (Intersection Observer):
    // Prefetch when link enters viewport
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const link = entry.target.getAttribute('href');
          prefetch(link);  // Load page in background
        }
      });
    });
       
    document.querySelectorAll('a[data-prefetch]').forEach((link) => {
      observer.observe(link);
    });
    
  3. On Idle (requestIdleCallback):
    // Prefetch during browser idle time
    const prefetchLinks = Array.from(document.querySelectorAll('a[data-prefetch]'));
       
    if ('requestIdleCallback' in window) {
      requestIdleCallback(() => {
        prefetchLinks.forEach((link) => {
          prefetch(link.href);
        });
      });
    }
    
  4. Resource Hints (HTML <link> tags):
    <!-- DNS prefetch (resolve domain early) -->
    <link rel="dns-prefetch" href="https://api.example.com">
       
    <!-- Preconnect (DNS + TCP + TLS handshake) -->
    <link rel="preconnect" href="https://cdn.example.com">
       
    <!-- Prefetch (low priority, for next navigation) -->
    <link rel="prefetch" href="/products.html">
       
    <!-- Preload (high priority, for current page) -->
    <link rel="preload" href="/critical.css" as="style">
       
    <!-- Prerender (DEPRECATED - loads entire page in background) -->
    <link rel="prerender" href="/checkout.html">
    

4. URL Structure and State Management

Anatomy of a URL:

https://example.com:443/products/123?color=red&size=M#reviews
└─┬─┘  └────┬─────┘└┬┘ └─────┬─────┘ └──────┬──────┘ └───┬───┘
Protocol  Domain   Port   Path      Query Params    Hash

URL Parameters for State:

// Search/filter state in URL (shareable, bookmarkable)
/products?category=shoes&brand=nike&price_max=100&sort=price_asc

// React Router implementation
import { useSearchParams } from 'react-router-dom';

function Products() {
  const [searchParams, setSearchParams] = useSearchParams();
  
  const category = searchParams.get('category') || 'all';
  const brand = searchParams.get('brand');
  const maxPrice = searchParams.get('price_max');
  
  const updateFilters = (newFilters) => {
    setSearchParams({ ...Object.fromEntries(searchParams), ...newFilters });
  };
  
  return (
    <div>
      <button onClick={() => updateFilters({ category: 'shoes' })}>
        Shoes
      </button>
      {/* URL updates: /products?category=shoes */}
    </div>
  );
}

Dynamic Routes (path parameters):

// Next.js file-based routing
pages/
  products/
    [id].js       // /products/123
    [slug].js     // /products/nike-shoes
    [...slug].js  // /products/shoes/nike/air-max (catch-all)

// pages/products/[id].js
import { useRouter } from 'next/router';

export default function Product() {
  const router = useRouter();
  const { id } = router.query;  // Get id from URL
  
  return <div>Product ID: {id}</div>;
}

Deep Links (link to specific app state):

Regular link:    https://example.com
Deep link:       https://example.com/user/123/posts/456/comments/789

↓ Navigates directly to nested view (comment 789 in post 456 by user 123)

Mobile App Deep Linking:

<!-- Universal Links (iOS) / App Links (Android) -->
<!-- Website link that opens mobile app if installed -->

<!-- apple-app-site-association (iOS) -->
{
  "applinks": {
    "apps": [],
    "details": [{
      "appID": "TEAMID.com.example.app",
      "paths": ["/products/*", "/user/*"]
    }]
  }
}

<!-- assetlinks.json (Android) -->
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.app",
    "sha256_cert_fingerprints": ["..."]
  }
}]

<!-- Usage: -->
<a href="https://example.com/products/123">
  Open Product
</a>

<!-- Behavior:
     - App installed: Opens app at /products/123
     - App not installed: Opens website at /products/123
     - Seamless fallback! -->

Custom URL Schemes:

<!-- App-specific protocol -->
<a href="myapp://products/123">Open in App</a>

<!-- Common schemes -->
<a href="spotify:track:123">Play Song</a>
<a href="slack://channel/123">Open Channel</a>

<!-- Fallback pattern -->
<a href="myapp://products/123" 
   onclick="setTimeout(() => window.location='https://example.com/products/123', 500)">
  Open Product
</a>
<!-- Tries app first, falls back to website after 500ms -->

6. SEO and Crawlability

Canonical URLs (prevent duplicate content):

<!-- Page accessible via multiple URLs -->
https://example.com/products/123
https://example.com/products/123?ref=email
https://example.com/products/123?utm_source=google

<!-- Specify canonical (original) URL -->
<link rel="canonical" href="https://example.com/products/123">

<!-- Search engines consolidate ranking signals to canonical URL -->

XML Sitemap (help crawlers discover pages):

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://example.com/</loc>
    <lastmod>2024-01-15</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://example.com/products</loc>
    <lastmod>2024-01-14</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>
</urlset>

Robots.txt (crawler directives):

User-agent: *
Allow: /

# Disallow admin pages
Disallow: /admin/
Disallow: /api/

# Sitemap location
Sitemap: https://example.com/sitemap.xml

Structured Data (rich snippets):

<!-- BreadcrumbList (navigation trail in search results) -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [{
    "@type": "ListItem",
    "position": 1,
    "name": "Home",
    "item": "https://example.com"
  },{
    "@type": "ListItem",
    "position": 2,
    "name": "Products",
    "item": "https://example.com/products"
  },{
    "@type": "ListItem",
    "position": 3,
    "name": "Shoes",
    "item": "https://example.com/products/shoes"
  }]
}
</script>

7. Accessibility and Navigation

Keyboard Navigation:

<!-- All links keyboard-accessible by default -->
<a href="/about">About</a>  <!-- Tab to focus, Enter to activate -->

<!-- Skip links (bypass navigation) -->
<a href="#main-content" class="skip-link">
  Skip to main content
</a>

<style>
  .skip-link {
    position: absolute;
    top: -40px;
    left: 0;
    z-index: 100;
  }
  .skip-link:focus {
    top: 0;  /* Visible on keyboard focus */
  }
</style>

<main id="main-content">
  <!-- Main content here -->
</main>

ARIA Attributes:

<!-- Current page indicator -->
<nav>
  <a href="/" aria-current="page">Home</a>
  <a href="/about">About</a>
  <a href="/contact">Contact</a>
</nav>

<!-- External link indicator -->
<a href="https://external.com" target="_blank" rel="noopener">
  External Site
  <span class="sr-only">(opens in new window)</span>
</a>

<!-- Disabled link (use button instead) -->
<button disabled>
  Unavailable Link
</button>
<!-- Don't use: <a href="#" class="disabled"> - still focusable, confusing -->

Focus Management:

// After client-side navigation, focus appropriate element
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function App() {
  const location = useLocation();
  
  useEffect(() => {
    // Focus main content after navigation
    const main = document.getElementById('main-content');
    if (main) {
      main.focus();
      main.scrollIntoView();
    }
  }, [location.pathname]);
  
  return <main id="main-content" tabIndex="-1">...</main>;
}

target=”_blank” Vulnerability:

<!-- VULNERABLE: Opener page accessible via window.opener -->
<a href="https://malicious.com" target="_blank">
  Click Me
</a>

<!-- Malicious site can do: -->
<script>
  window.opener.location = 'https://phishing-site.com';
  // User's original tab redirected to phishing site!
</script>

<!-- SECURE: Add rel="noopener noreferrer" -->
<a href="https://external.com" target="_blank" rel="noopener noreferrer">
  External Site
</a>

Open Redirect Vulnerability:

// VULNERABLE: User-controlled redirect
app.get('/redirect', (req, res) => {
  const url = req.query.url;
  res.redirect(url);  // Attacker can redirect to phishing site
});

// Attack: https://example.com/redirect?url=https://phishing.com

// SECURE: Whitelist allowed domains
app.get('/redirect', (req, res) => {
  const url = req.query.url;
  const allowedDomains = ['example.com', 'trusted-partner.com'];
  
  try {
    const parsedUrl = new URL(url, 'https://example.com');
    if (allowedDomains.includes(parsedUrl.hostname)) {
      res.redirect(url);
    } else {
      res.status(400).send('Invalid redirect URL');
    }
  } catch (error) {
    res.status(400).send('Invalid URL');
  }
});

Link Injection (XSS via href):

// VULNERABLE: User-controlled href
function UserProfile({ user }) {
  return <a href={user.website}>Website</a>;
  // If user.website = "javascript:alert('XSS')", code executes!
}

// SECURE: Validate protocol
function UserProfile({ user }) {
  const isSafeUrl = (url) => {
    try {
      const parsed = new URL(url);
      return ['http:', 'https:'].includes(parsed.protocol);
    } catch {
      return false;
    }
  };
  
  const website = isSafeUrl(user.website) ? user.website : null;
  
  return website ? <a href={website}>Website</a> : null;
}

Active Link Styling:

// React Router NavLink (automatic active class)
import { NavLink } from 'react-router-dom';

<NavLink 
  to="/about" 
  className={({ isActive }) => isActive ? 'active' : ''}
>
  About
</NavLink>

// CSS
.active {
  font-weight: bold;
  color: blue;
}

Loading States:

// Show loading indicator during navigation
import { useNavigation } from 'react-router-dom';

function App() {
  const navigation = useNavigation();
  const isNavigating = navigation.state === 'loading';
  
  return (
    <div>
      {isNavigating && <div className="loading-bar"></div>}
      <nav>
        <Link to="/about">About</Link>
      </nav>
    </div>
  );
}

Code Examples

// components/SmartLink.jsx - Production-ready link component
import React, { useEffect, useRef, useState } from 'react';
import { Link as RouterLink, useLocation } from 'react-router-dom';

/**
 * SmartLink - Enhanced link component with prefetching, security, and analytics
 * 
 * Features:
 * - Automatic external link detection
 * - Prefetching on hover/viewport
 * - Security attributes for external links
 * - Analytics tracking
 * - Accessibility enhancements
 */
export function SmartLink({ 
  to, 
  href,
  children, 
  prefetch = true,
  external = false,
  download = false,
  onClick,
  className,
  ...props 
}) {
  const linkRef = useRef(null);
  const [isPrefetched, setIsPrefetched] = useState(false);
  const location = useLocation();
  
  // Normalize URL
  const url = to || href;
  
  // Detect external links
  const isExternal = external || (url && (
    url.startsWith('http://') || 
    url.startsWith('https://') ||
    url.startsWith('mailto:') ||
    url.startsWith('tel:')
  ));
  
  // Prefetch on hover
  const handleMouseEnter = () => {
    if (prefetch && !isPrefetched && !isExternal) {
      prefetchRoute(url);
      setIsPrefetched(true);
    }
  };
  
  // Prefetch function (framework-specific implementation)
  const prefetchRoute = (route) => {
    // React Router v6+ with data router
    // or custom implementation
    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.href = route;
    document.head.appendChild(link);
  };
  
  // Intersection Observer for viewport-based prefetching
  useEffect(() => {
    if (!prefetch || isExternal || isPrefetched) return;
    
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            prefetchRoute(url);
            setIsPrefetched(true);
            observer.disconnect();
          }
        });
      },
      { rootMargin: '50px' }  // Prefetch 50px before entering viewport
    );
    
    if (linkRef.current) {
      observer.observe(linkRef.current);
    }
    
    return () => observer.disconnect();
  }, [url, prefetch, isExternal, isPrefetched]);
  
  // Analytics tracking
  const handleClick = (e) => {
    // Track click event
    if (window.gtag) {
      window.gtag('event', 'link_click', {
        link_url: url,
        link_text: typeof children === 'string' ? children : '',
        link_domain: isExternal ? new URL(url).hostname : window.location.hostname,
        outbound: isExternal
      });
    }
    
    // Custom onClick handler
    if (onClick) {
      onClick(e);
    }
  };
  
  // External link attributes
  const externalAttrs = isExternal ? {
    target: '_blank',
    rel: 'noopener noreferrer',
    // Add visual indicator
    'aria-label': `${children} (opens in new window)`
  } : {};
  
  // Download attribute
  const downloadAttr = download ? { download: typeof download === 'string' ? download : true } : {};
  
  // Internal link (use React Router)
  if (!isExternal && !download) {
    return (
      <RouterLink
        ref={linkRef}
        to={url}
        className={className}
        onClick={handleClick}
        onMouseEnter={handleMouseEnter}
        {...props}
      >
        {children}
      </RouterLink>
    );
  }
  
  // External or download link (use regular anchor)
  return (
    <a
      ref={linkRef}
      href={url}
      className={className}
      onClick={handleClick}
      {...externalAttrs}
      {...downloadAttr}
      {...props}
    >
      {children}
      {isExternal && (
        <svg 
          className="external-icon" 
          width="12" 
          height="12" 
          viewBox="0 0 12 12"
          aria-hidden="true"
          style={{ marginLeft: '4px', verticalAlign: 'text-top' }}
        >
          <path d="M6 1h5v5M11 1L5 7" stroke="currentColor" fill="none" />
        </svg>
      )}
    </a>
  );
}

// Usage examples
function Examples() {
  return (
    <div>
      {/* Internal link (prefetched) */}
      <SmartLink to="/products">
        Products
      </SmartLink>
      
      {/* External link (secure) */}
      <SmartLink href="https://external.com">
        External Site
      </SmartLink>
      
      {/* Download link */}
      <SmartLink href="/files/report.pdf" download="report.pdf">
        Download Report
      </SmartLink>
      
      {/* Email link */}
      <SmartLink href="mailto:contact@example.com">
        Email Us
      </SmartLink>
      
      {/* Disable prefetch */}
      <SmartLink to="/heavy-page" prefetch={false}>
        Heavy Page (no prefetch)
      </SmartLink>
    </div>
  );
}

// CSS for visual enhancements
/*
.external-icon {
  display: inline-block;
  margin-left: 4px;
  opacity: 0.7;
}

a:hover .external-icon {
  opacity: 1;
}

a:focus {
  outline: 2px solid blue;
  outline-offset: 2px;
}

a[download] {
  text-decoration: underline dotted;
}
*/

Example 2: URL State Management with Search Params

// pages/ProductsPage.jsx - Complex filtering with URL state
import React, { useEffect, useState } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';

function ProductsPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const navigate = useNavigate();
  
  // Parse URL parameters
  const filters = {
    category: searchParams.get('category') || 'all',
    brand: searchParams.getAll('brand') || [],  // Multiple values
    minPrice: parseInt(searchParams.get('min_price')) || 0,
    maxPrice: parseInt(searchParams.get('max_price')) || 1000,
    sort: searchParams.get('sort') || 'relevance',
    page: parseInt(searchParams.get('page')) || 1
  };
  
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(false);
  
  // Fetch products when filters change
  useEffect(() => {
    fetchProducts(filters);
  }, [searchParams]);  // Re-fetch when URL changes
  
  const fetchProducts = async (filters) => {
    setLoading(true);
    
    // Build API query
    const params = new URLSearchParams({
      category: filters.category,
      min_price: filters.minPrice,
      max_price: filters.maxPrice,
      sort: filters.sort,
      page: filters.page
    });
    
    // Add multiple brand filters
    filters.brand.forEach(b => params.append('brand', b));
    
    const response = await fetch(`/api/products?${params}`);
    const data = await response.json();
    
    setProducts(data.products);
    setLoading(false);
  };
  
  // Update single filter
  const updateFilter = (key, value) => {
    const newParams = new URLSearchParams(searchParams);
    
    if (value === null || value === '' || value === 'all') {
      newParams.delete(key);
    } else {
      newParams.set(key, value);
    }
    
    // Reset to page 1 when filters change
    if (key !== 'page') {
      newParams.delete('page');
    }
    
    setSearchParams(newParams);
  };
  
  // Toggle array filter (brands)
  const toggleBrand = (brand) => {
    const currentBrands = searchParams.getAll('brand');
    const newParams = new URLSearchParams(searchParams);
    
    // Remove all brand params
    newParams.delete('brand');
    
    // Add back brands (toggle selected brand)
    const updatedBrands = currentBrands.includes(brand)
      ? currentBrands.filter(b => b !== brand)
      : [...currentBrands, brand];
    
    updatedBrands.forEach(b => newParams.append('brand', b));
    
    // Reset to page 1
    newParams.delete('page');
    
    setSearchParams(newParams);
  };
  
  // Clear all filters
  const clearFilters = () => {
    setSearchParams({});
  };
  
  // Shareable URL
  const shareFilters = () => {
    const url = `${window.location.origin}${window.location.pathname}?${searchParams}`;
    navigator.clipboard.writeText(url);
    alert('Filter URL copied to clipboard!');
  };
  
  return (
    <div className="products-page">
      <aside className="filters">
        <h2>Filters</h2>
        
        {/* Category filter */}
        <div>
          <label>Category</label>
          <select 
            value={filters.category}
            onChange={(e) => updateFilter('category', e.target.value)}
          >
            <option value="all">All Categories</option>
            <option value="shoes">Shoes</option>
            <option value="clothing">Clothing</option>
            <option value="accessories">Accessories</option>
          </select>
        </div>
        
        {/* Brand filter (multiple selection) */}
        <div>
          <label>Brands</label>
          {['Nike', 'Adidas', 'Puma', 'Reebok'].map(brand => (
            <label key={brand}>
              <input
                type="checkbox"
                checked={filters.brand.includes(brand.toLowerCase())}
                onChange={() => toggleBrand(brand.toLowerCase())}
              />
              {brand}
            </label>
          ))}
        </div>
        
        {/* Price range */}
        <div>
          <label>Price Range: ${filters.minPrice} - ${filters.maxPrice}</label>
          <input
            type="range"
            min="0"
            max="1000"
            value={filters.minPrice}
            onChange={(e) => updateFilter('min_price', e.target.value)}
          />
          <input
            type="range"
            min="0"
            max="1000"
            value={filters.maxPrice}
            onChange={(e) => updateFilter('max_price', e.target.value)}
          />
        </div>
        
        {/* Sort */}
        <div>
          <label>Sort By</label>
          <select 
            value={filters.sort}
            onChange={(e) => updateFilter('sort', e.target.value)}
          >
            <option value="relevance">Relevance</option>
            <option value="price_asc">Price: Low to High</option>
            <option value="price_desc">Price: High to Low</option>
            <option value="newest">Newest</option>
          </select>
        </div>
        
        {/* Actions */}
        <button onClick={clearFilters}>Clear All Filters</button>
        <button onClick={shareFilters}>Share Filters</button>
      </aside>
      
      <main>
        <h1>Products</h1>
        
        {/* Active filters display */}
        <div className="active-filters">
          {filters.category !== 'all' && (
            <span>Category: {filters.category}</span>
          )}
          {filters.brand.map(b => (
            <span key={b}>Brand: {b}</span>
          ))}
        </div>
        
        {/* Product grid */}
        {loading ? (
          <div>Loading...</div>
        ) : (
          <>
            <div className="products-grid">
              {products.map(product => (
                <div key={product.id} className="product-card">
                  <h3>{product.name}</h3>
                  <p>${product.price}</p>
                  <a href={`/products/${product.id}`}>View Details</a>
                </div>
              ))}
            </div>
            
            {/* Pagination */}
            <div className="pagination">
              <button 
                disabled={filters.page === 1}
                onClick={() => updateFilter('page', filters.page - 1)}
              >
                Previous
              </button>
              <span>Page {filters.page}</span>
              <button onClick={() => updateFilter('page', filters.page + 1)}>
                Next
              </button>
            </div>
          </>
        )}
      </main>
    </div>
  );
}

// Example URLs generated:
// /products
// /products?category=shoes
// /products?category=shoes&brand=nike&brand=adidas
// /products?category=shoes&brand=nike&min_price=50&max_price=150&sort=price_asc
// /products?category=shoes&brand=nike&min_price=50&max_price=150&sort=price_asc&page=2

// Benefits:
// ✓ Shareable: Copy URL, share exact filter state
// ✓ Bookmarkable: Bookmark filtered view
// ✓ Back/Forward: Browser navigation works
// ✓ SEO: Different URLs for different filter combinations

Example 3: SEO-Optimized Navigation with Sitemap Generation

// scripts/generate-sitemap.js - Dynamic sitemap generation
const fs = require('fs');
const path = require('path');

// Fetch dynamic routes from database/API
async function getDynamicRoutes() {
  // Example: Fetch products, blog posts, categories
  const products = await fetch('https://api.example.com/products').then(r => r.json());
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());
  
  return [
    ...products.map(p => ({
      url: `/products/${p.slug}`,
      lastmod: p.updated_at,
      changefreq: 'weekly',
      priority: 0.8
    })),
    ...posts.map(p => ({
      url: `/blog/${p.slug}`,
      lastmod: p.published_at,
      changefreq: 'monthly',
      priority: 0.7
    }))
  ];
}

// Static routes
const staticRoutes = [
  { url: '/', changefreq: 'daily', priority: 1.0 },
  { url: '/about', changefreq: 'monthly', priority: 0.5 },
  { url: '/contact', changefreq: 'monthly', priority: 0.5 },
  { url: '/products', changefreq: 'daily', priority: 0.9 },
  { url: '/blog', changefreq: 'daily', priority: 0.8 }
];

// Generate sitemap XML
async function generateSitemap() {
  const domain = 'https://example.com';
  const dynamicRoutes = await getDynamicRoutes();
  const allRoutes = [...staticRoutes, ...dynamicRoutes];
  
  const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml"
        xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
        xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
${allRoutes.map(route => `  <url>
    <loc>${domain}${route.url}</loc>
    ${route.lastmod ? `<lastmod>${new Date(route.lastmod).toISOString().split('T')[0]}</lastmod>` : ''}
    ${route.changefreq ? `<changefreq>${route.changefreq}</changefreq>` : ''}
    ${route.priority ? `<priority>${route.priority}</priority>` : ''}
  </url>`).join('\n')}
</urlset>`;
  
  // Write to public directory
  fs.writeFileSync(path.join(__dirname, '../public/sitemap.xml'), sitemap);
  
  console.log(`✓ Sitemap generated with ${allRoutes.length} URLs`);
}

// Run sitemap generation
generateSitemap().catch(console.error);

// Add to package.json scripts:
// "build:sitemap": "node scripts/generate-sitemap.js"
// "build": "npm run build:sitemap && next build"
// components/SEOHead.jsx - SEO meta tags component
import React from 'react';
import { Helmet } from 'react-helmet-async';
import { useLocation } from 'react-router-dom';

export function SEOHead({
  title,
  description,
  image,
  type = 'website',
  author,
  publishedTime,
  modifiedTime,
  tags = []
}) {
  const location = useLocation();
  const domain = 'https://example.com';
  const url = `${domain}${location.pathname}`;
  
  const defaultDescription = 'Shop the best products at Example Store';
  const defaultImage = `${domain}/og-image.jpg`;
  
  return (
    <Helmet>
      {/* Basic meta tags */}
      <title>{title} | Example Store</title>
      <meta name="description" content={description || defaultDescription} />
      
      {/* Canonical URL */}
      <link rel="canonical" href={url} />
      
      {/* Open Graph (Facebook, LinkedIn) */}
      <meta property="og:type" content={type} />
      <meta property="og:url" content={url} />
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description || defaultDescription} />
      <meta property="og:image" content={image || defaultImage} />
      <meta property="og:site_name" content="Example Store" />
      {publishedTime && <meta property="article:published_time" content={publishedTime} />}
      {modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}
      {author && <meta property="article:author" content={author} />}
      {tags.length > 0 && tags.map(tag => (
        <meta key={tag} property="article:tag" content={tag} />
      ))}
      
      {/* Twitter Card */}
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:site" content="@examplestore" />
      <meta name="twitter:title" content={title} />
      <meta name="twitter:description" content={description || defaultDescription} />
      <meta name="twitter:image" content={image || defaultImage} />
      
      {/* Breadcrumb structured data */}
      <script type="application/ld+json">
        {JSON.stringify({
          "@context": "https://schema.org",
          "@type": "BreadcrumbList",
          "itemListElement": getBreadcrumbs(location.pathname)
        })}
      </script>
    </Helmet>
  );
}

function getBreadcrumbs(pathname) {
  const paths = pathname.split('/').filter(Boolean);
  const breadcrumbs = [
    { position: 1, name: 'Home', item: 'https://example.com' }
  ];
  
  let currentPath = '';
  paths.forEach((path, index) => {
    currentPath += `/${path}`;
    breadcrumbs.push({
      position: index + 2,
      name: path.charAt(0).toUpperCase() + path.slice(1),
      item: `https://example.com${currentPath}`
    });
  });
  
  return breadcrumbs;
}

// Usage
function ProductPage({ product }) {
  return (
    <>
      <SEOHead
        title={product.name}
        description={product.description}
        image={product.image}
        type="product"
        tags={product.categories}
      />
      <div>
        <h1>{product.name}</h1>
        {/* Product content */}
      </div>
    </>
  );
}

Common Mistakes

1. Using Buttons for Navigation

Wrong: Using <button> with onClick for navigation.

// BAD: Button for navigation
<button onClick={() => window.location = '/about'}>
  About Us
</button>

// Problems:
// ❌ Not keyboard-accessible with Tab key (buttons not in tab order for navigation)
// ❌ Not crawlable by search engines
// ❌ No context menu (right-click → Open in new tab)
// ❌ No URL preview on hover
// ❌ Screen readers don't announce as navigation

Correct: Use <a> or <Link> for navigation.

// GOOD: Link for navigation
<Link to="/about">
  About Us
</Link>

// Or with custom styling
<Link to="/about" className="button-style">
  About Us
</Link>

// Benefits:
// ✓ Keyboard accessible (Tab to focus)
// ✓ Crawlable (search engines follow links)
// ✓ Context menu available
// ✓ URL preview on hover
// ✓ Screen readers announce correctly

When to use buttons:

// Use <button> for actions that don't navigate
<button onClick={handleSubmit}>Submit Form</button>
<button onClick={toggleMenu}>Open Menu</button>
<button onClick={addToCart}>Add to Cart</button>

// Use <Link> for navigation
<Link to="/checkout">Checkout</Link>
<Link to="/products/123">View Product</Link>

Why it matters: Links and buttons have different semantics. Links navigate to new resources (should be <a>), buttons perform actions (should be <button>). Using the wrong element breaks accessibility, SEO, and user experience (keyboard shortcuts, context menu, etc.).

Wrong: External link without security attributes.

<a href="https://external.com" target="_blank">
  External Site
</a>

// Security vulnerability:
// External site can access window.opener
// Can redirect original tab to phishing site:
// window.opener.location = 'https://phishing.com';

Correct: Add rel="noopener noreferrer" to external links.

<a href="https://external.com" target="_blank" rel="noopener noreferrer">
  External Site
</a>

// rel="noopener": Prevents window.opener access (security)
// rel="noreferrer": Prevents Referer header (privacy)

// Both together: Full protection

Automatic fix in modern browsers:

<!-- Modern browsers automatically add rel="noopener" -->
<!-- But explicit is better for compatibility and clarity -->
<a href="https://external.com" target="_blank" rel="noopener noreferrer">

Why it matters: Without rel="noopener", external sites can access and manipulate your page via window.opener.location, enabling phishing attacks (redirect user’s original tab while they view external site). Always add rel="noopener noreferrer" to target="_blank" links.

Wrong: Using # for dummy links, breaks back button.

// BAD: Hash links for actions
<a href="#" onClick={(e) => {
  e.preventDefault();
  doSomething();
}}>
  Click Me
</a>

// Problems:
// ❌ Adds "#" to URL (breaks history)
// ❌ Page scrolls to top
// ❌ Still navigable with keyboard (shouldn't be a link)

Correct: Use <button> for actions, proper links for navigation.

// GOOD: Button for actions (no navigation)
<button onClick={doSomething}>
  Click Me
</button>

// GOOD: Real hash link for same-page navigation
<a href="#features">
  Jump to Features
</a>

// In SPA: Use router's hash linking
<Link to="/products#reviews">
  View Reviews
</Link>

Hash link implementation (React Router):

// Scroll to hash on route change
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function ScrollToHash() {
  const { hash } = useLocation();
  
  useEffect(() => {
    if (hash) {
      const element = document.querySelector(hash);
      if (element) {
        element.scrollIntoView({ behavior: 'smooth' });
      }
    }
  }, [hash]);
  
  return null;
}

// Usage
<Link to="/products#reviews">Reviews</Link>
// On navigation, scrolls to <div id="reviews">

Why it matters: href="#" is a hack—adds # to URL, breaks browser history, and scrolls page to top. If it’s not navigation, don’t use a link—use a <button>. If it IS navigation (same-page scroll), use proper hash with element ID.

Quiz

Question 1: Client-Side Routing vs Full Page Reload

Q: What’s the difference between <a href="/about"> and <Link to="/about"> in a React SPA? When does each cause a full page reload?

A:

<a href="/about"> (standard anchor tag):

<Link to="/about"> (React Router Link):

When <Link> causes full reload:

// Standard Link - client-side navigation
<Link to="/about">About</Link>

// Link with reloadDocument - forces full reload
<Link to="/about" reloadDocument>About</Link>

// External Link component - full reload
<Link to="https://external.com">External</Link>

Example:

function App() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      
      {/* Client-side navigation - count persists */}
      <Link to="/about">About (count preserved)</Link>
      
      {/* Full reload - count resets to 0 */}
      <a href="/about">About (count lost)</a>
    </div>
  );
}

When to use each:

Use <Link>:

Use <a>:

Progressive Enhancement:

// Next.js Link - best of both worlds
<Link href="/about">
  <a>About</a>
</Link>

// Without JavaScript: <a> works (full reload)
// With JavaScript: Link intercepts (client-side navigation)

Why it matters: Understanding the difference is critical for SPA development. Using <a> instead of <Link> causes unnecessary full page reloads, slow transitions, and lost state. But <a> is still necessary for external links, downloads, and progressive enhancement.

Question 2: URL Parameters vs Client State

Q: When should you store application state in URL parameters vs client state (Redux, React state)? What are the tradeoffs?

A:

URL Parameters (?category=shoes&sort=price):

Use when:

Examples:

// Search results
/search?q=shoes&category=running&brand=nike

// Product filtering
/products?price_min=50&price_max=150&color=red&size=M

// Pagination
/blog?page=3

// Sorting
/users?sort=name_asc

// Tabs/views
/dashboard?tab=analytics&date_range=30d

Advantages:

Disadvantages:


Client State (Redux, React state, Context):

Use when:

Examples:

// Modal visibility
const [isModalOpen, setIsModalOpen] = useState(false);

// Form draft (before submission)
const [formData, setFormData] = useState({ name: '', email: '' });

// Authentication state
const [user, setUser] = useState(null);

// Theme preference
const [theme, setTheme] = useState('dark');

// Dropdown expanded
const [isDropdownOpen, setIsDropdownOpen] = useState(false);

Advantages:

Disadvantages:


Hybrid Approach (best of both):

// Combine URL params + client state
function ProductsPage() {
  // Filters in URL (shareable)
  const [searchParams, setSearchParams] = useSearchParams();
  const category = searchParams.get('category') || 'all';
  
  // UI state in React (transient)
  const [isFilterPanelOpen, setIsFilterPanelOpen] = useState(false);
  
  // Data in Redux (global)
  const products = useSelector(state => state.products);
  
  return (
    <div>
      {/* URL param controls data */}
      <select value={category} onChange={(e) => 
        setSearchParams({ category: e.target.value })
      }>
        <option value="all">All</option>
        <option value="shoes">Shoes</option>
      </select>
      
      {/* Client state controls UI */}
      <button onClick={() => setIsFilterPanelOpen(!isFilterPanelOpen)}>
        Toggle Filters
      </button>
    </div>
  );
}

Decision Matrix:

State Type URL Params Client State
Search query
Filters/sorting
Pagination
Active tab Sometimes
Modal open
Form draft
Auth token
Theme ✓ (localStorage)
Dropdown state

Why it matters: URL parameters make state shareable, bookmarkable, and SEO-friendly, but expose data publicly and have length limits. Client state is private and flexible, but lost on refresh and not shareable. Use URL params for page-specific, shareable state (filters, search), client state for transient UI state (modals, dropdowns).

Q: What are the different link prefetching strategies (hover, viewport, idle), and when should you use each? What are the tradeoffs?

A:

1. Hover Prefetching (default in Next.js):

<Link href="/products" prefetch={true}>
  Products
</Link>

// Behavior: Prefetches when user hovers link
// Network request triggered ~300ms before click

Advantages:

Disadvantages:

When to use:


2. Viewport Prefetching (Intersection Observer):

// Prefetch when link enters viewport
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const href = entry.target.getAttribute('href');
      prefetch(href);
    }
  });
}, { rootMargin: '50px' });  // Prefetch 50px before visible

document.querySelectorAll('a[data-prefetch]').forEach((link) => {
  observer.observe(link);
});

Advantages:

Disadvantages:

When to use:


3. Idle Prefetching (requestIdleCallback):

// Prefetch during browser idle time
if ('requestIdleCallback' in window) {
  requestIdleCallback(() => {
    const links = document.querySelectorAll('a[data-prefetch]');
    links.forEach((link) => {
      prefetch(link.href);
    });
  }, { timeout: 2000 });
}

Advantages:

Disadvantages:

When to use:


4. Manual/Programmatic Prefetching:

// Prefetch after user action
function handleAddToCart() {
  addToCart(product);
  
  // User likely to checkout next
  prefetch('/checkout');
}

Advantages:

Disadvantages:

When to use:


Resource Hints (HTML <link> tags):

<!-- DNS prefetch (resolve domain early) -->
<link rel="dns-prefetch" href="https://api.example.com">
<!-- Cost: ~20-120ms DNS lookup -->
<!-- Use: External domains you'll need soon -->

<!-- Preconnect (DNS + TCP + TLS) -->
<link rel="preconnect" href="https://cdn.example.com">
<!-- Cost: ~100-300ms full handshake -->
<!-- Use: Critical third-party resources (fonts, CDN) -->

<!-- Prefetch (low priority, for NEXT navigation) -->
<link rel="prefetch" href="/products.html">
<!-- Cost: Full page download -->
<!-- Use: Likely next page user will visit -->

<!-- Preload (high priority, for CURRENT page) -->
<link rel="preload" href="/critical.css" as="style">
<!-- Cost: Full resource download -->
<!-- Use: Critical resources for current page -->

Bandwidth Considerations:

// Check connection quality before prefetching
if ('connection' in navigator) {
  const conn = navigator.connection;
  
  if (conn.effectiveType === '4g' && !conn.saveData) {
    // Fast connection, no data saver - prefetch aggressively
    enablePrefetch('viewport');
  } else if (conn.effectiveType === '3g') {
    // Slower connection - only hover prefetch
    enablePrefetch('hover');
  } else {
    // Slow connection or data saver - disable prefetch
    disablePrefetch();
  }
}

Decision Matrix:

Strategy Mobile Desktop Bandwidth Accuracy
Hover ✓✓✓ Low High
Viewport ✓✓✓ ✓✓ Medium Medium
Idle ✓✓ ✓✓ High Low
Manual ✓✓✓ ✓✓✓ Low Very High

Best Practice (Next.js default):

// Automatic hover prefetch on desktop
// Automatic viewport prefetch on mobile
// Respects user's data saver preference
<Link href="/products">Products</Link>

Why it matters: Prefetching improves perceived performance (instant navigation), but wastes bandwidth if overused. Hover prefetching is accurate but doesn’t work on mobile. Viewport prefetching works everywhere but can waste bandwidth. Idle prefetching is least accurate. Choose strategy based on device type, bandwidth constraints, and user behavior patterns.

References