Introduction
Over the years of building web applications, we've encountered (and made) plenty of mistakes. In this post, we'll share the most common pitfalls we've seen and how to avoid them in your projects.
1. Ignoring Mobile Users
The Mistake
Designing for desktop first (or desktop only) and treating mobile as an afterthought.
The Impact
- 60%+ of web traffic comes from mobile devices
- Poor mobile experience leads to high bounce rates
- Google's mobile-first indexing penalizes non-responsive sites
The Solution
Adopt mobile-first development:
/* Mobile-first approach */
.container {
padding: 1rem; /* Mobile default */
}
@media (min-width: 768px) {
.container {
padding: 2rem; /* Tablet */
}
}
@media (min-width: 1024px) {
.container {
padding: 3rem; /* Desktop */
}
}
Test on real devices - Emulators don't catch everything:
- Different screen sizes and resolutions
- Touch interactions and gestures
- Network conditions and performance
- Browser inconsistencies
2. Poor Error Handling
The Mistake
Assuming everything will work perfectly and not handling errors gracefully.
// Bad: No error handling
async function fetchUserData() {
const response = await fetch('/api/user')
const data = await response.json()
return data
}
The Impact
- Users see confusing error messages or blank screens
- Difficult to debug production issues
- Security vulnerabilities exposed through error details
The Solution
Implement comprehensive error handling:
// Good: Proper error handling
async function fetchUserData() {
try {
const response = await fetch('/api/user')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
return { success: true, data }
} catch (error) {
console.error('Error fetching user data:', error)
return {
success: false,
error: 'Failed to load user data. Please try again.'
}
}
}
Best practices:
- Show user-friendly error messages
- Log errors for debugging (use services like Sentry)
- Implement retry logic for transient failures
- Have fallback UI states
3. Neglecting Security
The Mistake
Treating security as something to "add later" instead of building it in from the start.
The Impact
- Data breaches and leaked user information
- SQL injection, XSS attacks, and other vulnerabilities
- Legal liability and damaged reputation
- Costly emergency fixes
The Solution
Implement security best practices:
// Validate and sanitize user input
import { z } from 'zod'
const userSchema = z.object({
email: z.string().email(),
age: z.number().min(18).max(120)
})
app.post('/api/user', async (req, res) => {
try {
const validatedData = userSchema.parse(req.body)
// Process validated data
} catch (error) {
res.status(400).json({ error: 'Invalid input' })
}
})
Security checklist:
- ✅ Use HTTPS everywhere
- ✅ Implement proper authentication (JWT, OAuth)
- ✅ Sanitize all user input
- ✅ Use parameterized queries to prevent SQL injection
- ✅ Set secure HTTP headers (CSP, HSTS, X-Frame-Options)
- ✅ Keep dependencies updated
- ✅ Implement rate limiting
- ✅ Hash passwords with bcrypt or argon2
4. Skipping Accessibility
The Mistake
Not considering users with disabilities or those using assistive technologies.
The Impact
- Excluding 15% of the global population (1 billion+ people)
- Legal risks (ADA compliance requirements)
- Poor SEO (accessibility and SEO overlap significantly)
- Reduced usability for everyone
The Solution
Build with accessibility in mind:
<!-- Bad: No accessibility -->
<div onclick="submitForm()">Submit</div>
<!-- Good: Accessible -->
<button
type="submit"
aria-label="Submit form"
onclick="submitForm()"
>
Submit
</button>
<!-- Better: Semantic HTML -->
<form onsubmit="handleSubmit(event)">
<label for="email">
Email Address
<input
id="email"
type="email"
required
aria-describedby="email-help"
/>
</label>
<span id="email-help" class="help-text">
We'll never share your email
</span>
<button type="submit">Submit</button>
</form>
Accessibility essentials:
- Use semantic HTML elements
- Provide text alternatives for images
- Ensure keyboard navigation works
- Maintain sufficient color contrast (WCAG 4.5:1)
- Test with screen readers
- Add ARIA labels when needed (but prefer semantic HTML)
5. No Performance Budget
The Mistake
Adding features and libraries without considering their impact on performance.
The Impact
- Slow load times drive users away
- Poor Core Web Vitals hurt SEO
- Increased hosting costs
- Bad user experience on slower connections
The Solution
Set and enforce performance budgets:
// webpack.config.js
module.exports = {
performance: {
maxAssetSize: 250000, // 250KB
maxEntrypointSize: 250000,
hints: 'error'
}
}
Before adding a new library, ask:
- Do we really need this dependency?
- Is there a lighter alternative?
- Can we implement this ourselves?
- What's the bundle size impact?
Example: Date handling
// Heavy: moment.js (72KB minified)
import moment from 'moment'
const formatted = moment().format('YYYY-MM-DD')
// Light: native Intl API (0KB - built into browsers)
const formatted = new Intl.DateTimeFormat('en-CA').format(new Date())
// Or use date-fns (light, tree-shakeable)
import { format } from 'date-fns'
const formatted = format(new Date(), 'yyyy-MM-dd')
Bonus: Not Planning for Scalability
The Mistake
Building for today's users without considering growth.
The Solution
Design with scale in mind:
- Use database indexes appropriately
- Implement caching layers (Redis, CDN)
- Design API with rate limiting
- Use message queues for async tasks
- Plan for horizontal scaling
- Monitor performance metrics
// Example: Pagination for large datasets
app.get('/api/posts', async (req, res) => {
const page = parseInt(req.query.page) || 1
const limit = 20
const offset = (page - 1) * limit
const posts = await db.posts
.findMany({
take: limit,
skip: offset,
orderBy: { createdAt: 'desc' }
})
res.json({
data: posts,
pagination: {
page,
limit,
hasMore: posts.length === limit
}
})
})
Conclusion
Learning from mistakes is crucial for growth as a developer. The key is to:
- Start with best practices - Don't wait to add them later
- Test early and often - On real devices, with real users
- Measure everything - Performance, accessibility, security
- Stay updated - Web development evolves quickly
- Learn from others - Read post-mortems and case studies
At CodeInv, we've learned these lessons the hard way so you don't have to. Whether you're starting a new project or maintaining an existing one, we can help you avoid these common pitfalls and build something great.
