Native browser APIs can replace several npm packages you install by default, including lodash's cloneDeep, uuid, and moment for formatting. Modern browsers now handle deep cloning, unique IDs, HTTP requests, and date formatting without extra code, which means fewer dependencies, smaller bundles, and less to maintain. Here are 7 native browser APIs worth using in 2026, with a short example for each, plus the cases where the package still wins.
The primary keyword “native browser APIs” is placed in the first sentence, which directly answers the topic for featured snippets and AI Overviews. The introduction also naturally connects to AI is Transforming Flutter App Development, showing how modern development practices and AI-assisted workflows are changing the way developers build faster, lighter applications.
1. StructuredClone() Replaces lodash cloneDeep
structuredClone() makes a deep copy of an object, so it can replace lodash cloneDeep in most cases. It copies nested objects, arrays, dates, maps, and sets, and it handles circular references.
// Before
import cloneDeep from 'lodash/cloneDeep';
const copy = cloneDeep(user);
// After
const copy = structuredClone(user);
Support: All modern browsers and Node.js 17+.
Limit: It can't copy functions or DOM nodes, and class instances lose their methods. Keep cloneDeep if you need those.
2. Crypto.randomUUID() Replaces uuid
crypto.randomUUID() generates a secure, random version 4 UUID in one line, so you no longer need the uuid package for everyday IDs.
// Before
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
// After
const id = crypto.randomUUID();
// "3b241101-e2bb-4255-8caf-4136c566a962"
Support: All modern browsers and Node.js 14.17+.
Limit: It only works on secure origins (HTTPS or localhost) and only creates v4 UUIDs. Keep uuid if you need v1, v5, or v7.
3. Fetch With AbortSignal.timeout() Replaces axios
For simple requests, the built-in fetch API replaces axios. Add AbortSignal.timeout() and you also get a request timeout without extra code.
// Before
const { data } = await axios.get('/api/users', { timeout: 5000 });
// After
const res = await fetch('/api/users', {
signal: AbortSignal.timeout(5000),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
Support: All modern browsers and Node.js 18+.
Limit: fetch doesn't throw on 404 or 500 responses, so check res.ok yourself. Keep axios if you rely on interceptors or automatic retries.
4. URLSearchParams Replaces qs and query-string
URLSearchParams reads, builds, and updates query strings natively, so it covers most uses of qs and query-string.
// Before
import qs from 'qs';
const query = qs.stringify({ q: 'web apis', page: 2 });
// After
const params = new URLSearchParams({ q: 'web apis', page: 2 });
params.toString(); // "q=web+apis&page=2"
const url = new URL('https://example.com/search?q=web+apis&page=2');
url.searchParams.get('q'); // "web apis"
Support: All modern browsers and Node.js.
Limit: It doesn't handle nested objects or arrays the way qs does. Keep qs if your API expects formats like a[b]=1.
5. Intl.DateTimeFormat and Intl.RelativeTimeFormat Replace moment for Formatting
The built-in Intl APIs format dates and relative times in the user's own language, which covers most of what people use moment for.
// Before
moment(date).format('MMM D, YYYY');
moment(date).fromNow();
// After
new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' }).format(date);
// "Oct 1, 2026"
new Intl.RelativeTimeFormat('en', { numeric: 'auto' }).format(-1, 'day');
// "yesterday"
Support: All modern browsers and Node.js.
Limit: Intl formats dates but doesn't do date math, parsing, or time zone conversion. Keep date-fns or a similar library for those.
6. Object.groupBy() Replaces lodash groupBy
Object.groupBy() sorts the items in an array into groups based on a rule you give it, so it replaces lodash groupBy in most cases.
const orders = [
{ id: 1, status: 'paid' },
{ id: 2, status: 'pending' },
{ id: 3, status: 'paid' },
];
// Before
import groupBy from 'lodash/groupBy';
const byStatus = groupBy(orders, 'status');
// After
const byStatus = Object.groupBy(orders, (order) => order.status);
// { paid: [{...}, {...}], pending: [{...}] }
Support: Chrome 117+, Firefox 119+, Safari 17.4+, and Node.js 21+. Check MDN if you support older browsers.
Limit: It doesn't accept lodash's 'status' shorthand, so you pass a function. It returns an object with no prototype, and Map.groupBy() is the option when your keys aren't strings.
7. <dialog> and Popover Replace Basic Modal and Tooltip Packages
The built-in <dialog> element and the Popover API show modals and pop-up content with no JavaScript library. <dialog> handles focus trapping, the Escape key, and the backdrop for you, so it can replace simple packages like react-modal.
<dialog id="delete-dialog">
<p>Delete this item?</p>
<form method="dialog">
<button>Cancel</button>
</form>
</dialog>
<button id="open">Delete</button>
<button popovertarget="tip">Info</button>
<div id="tip" popover>Saved automatically.</div>
<script>
document.getElementById('open').onclick = () =>
document.getElementById('delete-dialog').showModal();
</script>
Support: <dialog> works in all modern browsers. The Popover API is newer, so check MDN for the versions you support.
Limit: A popover appears in the center of the viewport by default, and placing it next to its trigger needs CSS anchor positioning, which was not Baseline as of September 2026. Keep a positioning library like Floating UI for tooltips and dropdowns that must sit beside their buttons.
When Should You Keep the npm Package?
Keep the package when the native API can't match the features you actually use. Replace a dependency only if the native version covers everything you need.
- axios: interceptors and automatic retries
- date-fns: date math, parsing, and time zones
- qs: nested objects and arrays in query strings
- uuid: UUID versions other than v4
- Floating UI: tooltips and dropdowns anchored to a button
Before you remove anything, check each API's support on MDN or Can I Use against the browsers you target, especially when building AI-Powered Mobile Apps where compatibility can affect how features work across different devices and platforms.
Quick Reference: npm Package vs Native API
| npm package | Native API | Main limit |
|---|---|---|
| lodash cloneDeep | structuredClone() | No functions or DOM nodes |
| uuid | crypto.randomUUID() | HTTPS only, v4 only |
| axios | fetch + AbortSignal.timeout() | No interceptors |
| qs, query-string | URLSearchParams | No nested objects |
| moment | Intl.DateTimeFormat | Formatting only, no date math |
| lodash groupBy | Object.groupBy() | Needs newer browsers |
| react-modal | and Popover | Popover positioning needs extra CSS |
Final Thoughts
Native browser APIs let you remove several dependencies without losing the features you use: structuredClone(), crypto.randomUUID(), fetch, URLSearchParams, Intl, Object.groupBy(), and
. Start with one, test it in your target browsers, and keep a package only where it still earns its place. This is also worth considering when exploring Why Do Apps Ask for Permissions, as understanding native browser capabilities can help clarify which features require user access and why.Which npm package did you remove first? Share it in the comments, and follow for more practical JavaScript posts.
Top comments (0)