Introduction
The same problems often recur in web projects. Here are five common mistakes and practical ways to avoid them.
1. Ignoring Mobile Users
The Mistake
Designing for desktop first (or desktop only) and treating mobile as an afterthought.
The Impact
- Phone users struggle to read content or complete tasks
- An awkward mobile layout can cause visitors to leave
- Content missing from the mobile version may also be unavailable to search engines
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
- Detailed error messages can expose sensitive system information
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
Validate submitted data on the server:
// Check the input format and allowed values
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
- ✅ Use authentication and access controls appropriate to the application
- ✅ Validate all user input
- ✅ Use parameterised 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
- Some visitors cannot use the service
- Forms and navigation become difficult to use
- Important information may be unavailable to assistive technology
- Temporary impairments can also make the site harder to use
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 use your email address to reply to your enquiry
</span>
<button type="submit">Submit</button>
</form>
Accessibility essentials:
- Use semantic HTML elements
- Provide text alternatives for images
- Ensure keyboard navigation works
- Check text and interface contrast against the relevant WCAG requirements
- 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 performance makes the site harder to use
- 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
// A larger dependency: Moment.js
import moment from 'moment'
const formatted = moment().format('YYYY-MM-DD')
// The built-in Intl API needs no additional library
const formatted = new Intl.DateTimeFormat('en-CA').format(new Date())
// Another option: import the function you need from date-fns
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
Simple working habits can prevent many of these problems:
- Start with best practices - Don't wait to add them later
- Test early and often - On real devices, with real users
- Check the result - Assess performance, accessibility, and security
- Stay updated - Web development evolves quickly
- Learn from others - Read post-mortems and case studies
At CodeInv, we help work through these questions when building new applications and improving existing software. Get in touch if you would like to discuss your project.
