Docs
18.4-URL-and-History-APIs
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