Things Web Developers Should Stop Doing
We've all inherited bad habits — some from tutorials, some from Stack Overflow answers written in 2013, some from our own tired brains at 2 AM. This is a no-nonsense list of practices the web development community needs to collectively retire. No judgment. Just hard truths.
Using console.log() as Your Debugger
Scattering console.log("here") and console.log(data) across your codebase isn't debugging — it's guessing with extra steps. Your browser has a full-featured debugger with breakpoints, watch expressions, call stacks, and step-through execution. Use it.
Shipping console.log statements to production leaks implementation details and pollutes the user's DevTools. Set up an ESLint rule to catch them before they ever reach a PR.
console.log("reached here")
console.log(JSON.stringify(res))
Use debugger; keyword intentionally
Strip logs via ESLint / build step
Storing Sensitive Data in localStorage
LocalStorage is not a vault. It's a plain-text key-value store accessible to any JavaScript running on your page — including third-party scripts, browser extensions, and XSS payloads. Storing JWTs, tokens, or user PII here is an open invitation.
For authentication tokens, prefer HttpOnly cookies. They can't be accessed by JavaScript at all, making XSS attacks far less damaging.
localStorage.setItem('userId', id)
Keep sensitive state server-side
Ignoring Accessibility Until Launch Day
Accessibility retrofitted is accessibility half-done. Semantic HTML, ARIA roles, focus management, and keyboard navigation are not an afterthought — they're architectural decisions. Bolting them on post-launch costs 10× more than building them in from the start.
The WCAG 2.2 guidelines aren't just about screen readers. They improve usability for everyone: keyboard users, people on slow connections, users with temporary impairments. Run Lighthouse and axe DevTools on every PR.
Reaching for a Framework for Everything
Not every project needs React. Not every interaction needs a state management library. The instinct to npx create-react-app a landing page with three static sections and a contact form is costing your users 200 KB of JavaScript they didn't ask for.
Vanilla JS, HTML, and CSS have evolved dramatically. Before pulling in a dependency, ask: "Can the platform handle this natively?" More often than you think, the answer is yes.
"The best framework is no framework — until the complexity demands one."
Writing Inline Styles in JSX (At Scale)
Inline styles are fine for quick prototypes. In production codebases they become unmaintainable: no theming, no media queries, no pseudo-selectors, no deduplication. They bypass the cascade entirely and make design consistency nearly impossible.
Adopt a consistent styling strategy — CSS Modules, Tailwind, styled-components, or plain CSS with BEM. Whatever you choose, be consistent across the codebase.
// Stop this <div style={{ fontSize: '14px', color: '#333', marginTop: '8px' }}> // Do this <div className={styles.caption}>
Skipping Image Optimization Entirely
Serving a 4 MB PNG where a 120 KB WebP would do is one of the most common and most damaging performance mistakes on the web. Images are typically the largest contributors to page weight and Largest Contentful Paint (LCP).
Use modern formats (WebP, AVIF), appropriate dimensions, lazy loading for below-the-fold images, and srcset for responsive images. Next.js's <Image> component and tools like Squoosh or Sharp make this trivial — there's no excuse.
(4.2 MB, wrong size, no lazy load)
width="800" height="450"
srcset="hero-400.avif 400w, ..." />
Trusting Client-Side Validation Alone
Client-side validation is UX. Server-side validation is security. A user with DevTools open (or curl) can bypass every form validation you've written in React in under 30 seconds.
Always validate, sanitize, and authenticate on the server. The frontend validation is a courtesy layer. The backend is the law.
Committing Without Meaningful Messages
Your git history is documentation. "fix", "wip", "asdf", and "updates" are not commit messages — they're noise that makes debugging, reverting, and onboarding painful for everyone, including future you.
Follow Conventional Commits or at minimum write a message that answers: what changed and why? A well-maintained git log is worth more than most inline comments.
wip
stuff
final final FINAL
feat(cart): persist items to localStorage
refactor(api): extract fetchUser helper
Abusing useEffect for Everything in React
The useEffect hook is not a generic lifecycle method. Reaching for it to sync state, derive computed values, or respond to user events is a sign that something has gone wrong in your mental model of React.
If you're computing a value from props or state — just compute it during render. If you're handling a user event — do it in the event handler. useEffect is for synchronizing with external systems: the DOM, browser APIs, subscriptions, timers.
// Stop — unnecessary effect useEffect(() => { setFullName(first + ' ' + last); }, [first, last]); // Do — derive during render const fullName = first + ' ' + last;
Shipping Without Error Boundaries or Fallbacks
A single unhandled JavaScript error in a React tree can take down your entire UI — leaving users staring at a blank white page with zero context. This is unacceptable in production.
Implement React Error Boundaries around critical sections of your app. Add global window.onerror and unhandledrejection listeners. Integrate an error monitoring tool (Sentry, Datadog) so you hear about crashes before your users do.
Every async function should have a catch. Every data fetch should handle loading and error states. Assume things will fail — because they will.
Good developers write code that works.
Great developers write code that fails gracefully.









