Guide
1 min read18 headings
11.4 URL and History APIs
Overview
The URL API provides utilities for parsing, constructing, and manipulating URLs. The History API allows manipulation of the browser session history, enabling Single-Page Application (SPA) navigation.
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā URL Anatomy ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā https://user:pass@api.example.com:8080/path/to/page?q=1#sec ā
ā āāāā⤠āāāāāāāā⤠āāāāāāāāāāāāāā⤠āāāā¤āāāāāāāāāāāāāā¤āāāā¤āāā⤠ā
ā protocol username host port pathname search hash ā
ā password ā
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā url.protocol ā 'https:' ā ā
ā ā url.username ā 'user' ā ā
ā ā url.password ā 'pass' ā ā
ā ā url.hostname ā 'api.example.com' ā ā
ā ā url.host ā 'api.example.com:8080' ā ā
ā ā url.port ā '8080' ā ā
ā ā url.pathname ā '/path/to/page' ā ā
ā ā url.search ā '?q=1' ā ā
ā ā url.hash ā '#sec' ā ā
ā ā url.origin ā 'https://api.example.com:8080' ā ā
ā ā url.href ā (full URL) ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
URL Constructor
// Create from full URL
const url = new URL('https://example.com/path?query=value');
// Create with base URL
const url2 = new URL('/api/users', 'https://example.com');
// Result: https://example.com/api/users
// Modify URL parts
url.pathname = '/new-path';
url.hash = '#section';
console.log(url.href);
URLSearchParams
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā URLSearchParams Methods ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā Creating: ā
ā āāāāāāāāā ā
ā new URLSearchParams() // Empty ā
ā new URLSearchParams('?a=1&b=2') // From string ā
ā new URLSearchParams({ a: '1' }) // From object ā
ā new URLSearchParams([['a','1']]) // From entries ā
ā ā
ā Methods: ā
ā āāāāāāāā ā
ā .get(name) ā Get first value ā
ā .getAll(name) ā Get all values as array ā
ā .has(name) ā Check if param exists ā
ā .set(name, value) ā Set value (replaces all) ā
ā .append(name, value)ā Add value (allows duplicates) ā
ā .delete(name) ā Remove all with name ā
ā .toString() ā Serialize to string ā
ā ā
ā Iteration: ā
ā āāāāāāāāāā ā
ā .keys() ā Iterator of names ā
ā .values() ā Iterator of values ā
ā .entries() ā Iterator of [name, value] ā
ā .forEach(fn) ā Iterate all entries ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
URLSearchParams Examples
// Parse current URL query params
const params = new URLSearchParams(window.location.search);
// Get values
const page = params.get('page'); // '1' or null
const tags = params.getAll('tag'); // ['js', 'web']
// Modify
params.set('page', '2');
params.append('sort', 'date');
params.delete('old');
// Build URL
const url = new URL('https://api.example.com/search');
url.search = params.toString();
History API
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā History API Overview ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā Browser History Stack: ā
ā āāāāāāāāāāāāāāāāāāāāāā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā Page 1 ā Page 2 ā Page 3 ā Page 4 ā ā Current ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā ā
ā history.back() ā
ā history.forward() ā
ā history.go(n) ā
ā ā
ā Properties: ā
ā āāāāāāāāāāā ā
ā history.length ā Number of entries in history ā
ā history.state ā State object for current entry ā
ā history.scrollRestoration ā 'auto' or 'manual' ā
ā ā
ā Methods: ā
ā āāāāāāāā ā
ā history.back() ā Go back one page ā
ā history.forward() ā Go forward one page ā
ā history.go(n) ā Go n pages (+ forward, - back) ā
ā history.pushState() ā Add new entry ā
ā history.replaceState()ā Replace current entry ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
pushState vs replaceState
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā pushState vs replaceState ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā pushState(state, title, url) ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ⢠Adds NEW entry to history stack ā
ā ⢠Back button returns to previous page ā
ā ⢠history.length increases ā
ā ā
ā Before: [Page1, Page2, Page3*] ā
ā After: [Page1, Page2, Page3, NewPage*] ā
ā ā
ā replaceState(state, title, url) ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ⢠REPLACES current entry in history ā
ā ⢠Back button skips replaced page ā
ā ⢠history.length stays same ā
ā ā
ā Before: [Page1, Page2, Page3*] ā
ā After: [Page1, Page2, NewPage*] ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
State Object
// pushState(stateObject, title, url)
history.pushState(
{ page: 'products', id: 123 }, // State data (serializable)
'', // Title (most browsers ignore)
'/products/123' // New URL (same origin only)
);
// Access state later
console.log(history.state); // { page: 'products', id: 123 }
// replaceState works the same
history.replaceState({ updated: true }, '', '/products/123/edit');
popstate Event
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā popstate Event ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā Fired when: ā
ā ⢠User clicks back/forward buttons ā
ā ⢠history.back(), forward(), go() called ā
ā ā
ā NOT fired when: ā
ā ⢠pushState() or replaceState() called ā
ā ā
ā window.addEventListener('popstate', (event) => { ā
ā console.log('State:', event.state); ā
ā console.log('URL:', location.href); ā
ā ā
ā // Handle navigation ā
ā if (event.state?.page === 'products') { ā
ā showProductPage(event.state.id); ā
ā } ā
ā }); ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Location Object
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā window.location ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā Properties (read/write): ā
ā āāāāāāāāāāāāāāāāāāāāāāāā ā
ā location.href ā Full URL (setting navigates) ā
ā location.protocol ā 'http:' or 'https:' ā
ā location.host ā hostname:port ā
ā location.hostname ā Domain name ā
ā location.port ā Port number ā
ā location.pathname ā Path after domain ā
ā location.search ā Query string with ? ā
ā location.hash ā Fragment with # ā
ā ā
ā Methods: ā
ā āāāāāāāā ā
ā location.assign(url) ā Navigate (adds to history) ā
ā location.replace(url) ā Navigate (replaces history) ā
ā location.reload() ā Reload current page ā
ā location.toString() ā Returns href ā
ā ā
ā Navigation Examples: ā
ā āāāāāāāāāāāāāāāāāāāā ā
ā location.href = '/new-page'; // Navigate ā
ā location = '/new-page'; // Same as above ā
ā location.hash = '#section'; // Change hash only ā
ā location.search = '?page=2'; // Change query ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Common URL Patterns
Building Query Strings
function buildQueryString(params) {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value === undefined || value === null) return;
if (Array.isArray(value)) {
value.forEach((v) => searchParams.append(key, v));
} else {
searchParams.set(key, value);
}
});
return searchParams.toString();
}
// Usage
const query = buildQueryString({
search: 'hello world',
tags: ['js', 'web'],
page: 1,
});
// Result: search=hello+world&tags=js&tags=web&page=1
Parsing URLs
function parseURL(urlString) {
const url = new URL(urlString);
const params = Object.fromEntries(url.searchParams);
return {
protocol: url.protocol,
host: url.host,
pathname: url.pathname,
params,
hash: url.hash.slice(1),
};
}
SPA Routing Pattern
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā SPA Router Pattern ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā 1. Define Routes ā
ā āāāāāāāāāāāāāāāā ā
ā const routes = { ā
ā '/': HomePage, ā
ā '/products': ProductsPage, ā
ā '/products/:id': ProductDetailPage ā
ā }; ā
ā ā
ā 2. Handle Navigation ā
ā āāāāāāāāāāāāāāāāāāāā ā
ā function navigate(path, state = {}) { ā
ā history.pushState(state, '', path); ā
ā renderCurrentRoute(); ā
ā } ā
ā ā
ā 3. Listen for Back/Forward ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā window.addEventListener('popstate', () => { ā
ā renderCurrentRoute(); ā
ā }); ā
ā ā
ā 4. Match and Render ā
ā āāāāāāāāāāāāāāāāāāā ā
ā function renderCurrentRoute() { ā
ā const path = location.pathname; ā
ā const Component = matchRoute(path); ā
ā Component.render(); ā
ā } ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
URL Encoding
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā URL Encoding Methods ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā encodeURI(str) ā
ā āāāāāāāāāāāāāā ā
ā ⢠Encodes full URI ā
ā ⢠Preserves: : / ? # [ ] @ ! $ & ' ( ) * + , ; = ā
ā ⢠Use for: Complete URLs ā
ā ā
ā encodeURIComponent(str) ā
ā āāāāāāāāāāāāāāāāāāāāāāā ā
ā ⢠Encodes URI component ā
ā ⢠Encodes everything except: A-Z a-z 0-9 - _ . ! ~ * ' ( ) ā
ā ⢠Use for: Query parameters, path segments ā
ā ā
ā Examples: ā
ā āāāāāāāāā ā
ā encodeURI('https://example.com/path?name=John Doe') ā
ā ā 'https://example.com/path?name=John%20Doe' ā
ā ā
ā encodeURIComponent('name=John&age=30') ā
ā ā 'name%3DJohn%26age%3D30' ā
ā ā
ā // Building URL safely ā
ā const param = encodeURIComponent(userInput); ā
ā const url = `https://api.com/search?q=${param}`; ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
hashchange Event
// Listen for hash changes
window.addEventListener('hashchange', (event) => {
console.log('Old URL:', event.oldURL);
console.log('New URL:', event.newURL);
console.log('New hash:', location.hash);
// Handle hash-based navigation
handleHashRoute(location.hash);
});
// Change hash (triggers event)
location.hash = '#/products';
// Read hash
const hash = location.hash.slice(1); // Remove # prefix
Best Practices
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Best Practices ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā URL Construction: ā
ā āāāāāāāāāāāāāāāāā ā
ā ā Use URL constructor for parsing ā
ā ā Use URLSearchParams for query strings ā
ā ā Always encode user input with encodeURIComponent ā
ā ā Validate URLs before using ā
ā ā
ā History API: ā
ā āāāāāāāāāāāā ā
ā ā Keep state objects small and serializable ā
ā ā Always handle popstate for SPAs ā
ā ā Use replaceState for redirects ā
ā ā Consider scroll position management ā
ā ā
ā Security: ā
ā āāāāāāāāā ā
ā ā Validate URLs before navigation ā
ā ā Avoid using user input in location.href directly ā
ā ā Check origin before processing postMessage ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Browser Compatibility
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| URL | 32+ | 26+ | 7+ | 12+ |
| URLSearchParams | 49+ | 44+ | 10.1+ | 17+ |
| history.pushState | 5+ | 4+ | 5+ | 12+ |
| popstate event | 5+ | 4+ | 5+ | 12+ |
Key Takeaways
- URL API - Parse and construct URLs safely
- URLSearchParams - Easy query string manipulation
- pushState - Add to history without reload
- replaceState - Modify current history entry
- popstate - Handle back/forward navigation
- location - Navigate and read current URL
- Always encode - Use encodeURIComponent for user input