Building Production Next.js Apps: Real-World Lessons and Best Practices
Practical lessons from building 20+ Next.js production applications. Learn how to handle slow connections, optimize for mobile, integrate payments, and deploy reliably.
Building Production Next.js Apps: Real-World Lessons and Best Practices
We've built over 20 Next.js applications in production. E-commerce platforms, school management systems, fintech dashboards, and business websites.
Here's what actually matters when you're building for real users, not what the documentation tells you.
Why We Chose Next.js
Before Next.js, we were building separate React frontends and Node.js backends. Every project meant setting up two codebases, handling CORS, managing deployments twice, and explaining to clients why they needed to pay for two servers.
Next.js solved this. One codebase, one deployment, server and client code together. For a small team, this was huge.
But the real win? Image optimization. Next.js automatically optimizes images. This alone improved our clients' websites significantly.
The Internet Problem
Let's talk about the elephant in the room: not everyone has fast internet.
Your users might have decent 4G. They might be on 2G. They might have good internet that cuts out randomly. You can't ignore this.
What We Do:
Aggressive Image Optimization
import Image from 'next/image'
// Use priority for above-fold images
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority
quality={75} // Lower than default 100
/>
// Lazy load everything else
<Image
src="/product.jpg"
alt="Product"
width={400}
height={400}
loading="lazy"
quality={70}
/>
We set quality to 70-75 instead of the default 100. Users can't tell the difference, but the file sizes are much smaller.
Minimal JavaScript We use Server Components by default. Only add 'use client' when absolutely necessary. Less JavaScript means faster load times on slow connections.
Loading States Everywhere When internet is slow, users need to know something is happening:
export default function ProductList() {
const [loading, setLoading] = useState(true)
if (loading) {
return (
<div className="flex justify-center py-20">
<div className="animate-spin rounded-full h-12 w-12 border-4 border-blue-600 border-t-transparent"></div>
</div>
)
}
return <div>{/* Your content */}</div>
}
Simple spinner. Users know the app is working, not frozen.
Mobile-First Is Not Optional
Over 80% of our users access our apps on mobile. Not desktop. Not tablet. Mobile phones.
What This Means:
Touch Targets Must Be Big Buttons need to be at least 44x44 pixels. Smaller buttons lead to frustrated users clicking the wrong thing.
// Bad
<button className="px-2 py-1 text-sm">Submit</button>
// Good
<button className="px-6 py-3 text-base">Submit</button>
Forms Need Mobile Keyboards Use the right input types so mobile keyboards show the right layout:
<input type="tel" /> // Shows number pad
<input type="email" /> // Shows @ symbol
<input type="number" /> // Shows numbers
Small thing, huge impact on user experience.
Test on Actual Phones Chrome DevTools mobile view is not enough. We test on actual Android phones. Cheap ones. Because that's what most users have.
Payment Integration Reality
Every client wants payment integration. Stripe, PayPal, or local payment providers.
Here's what the documentation doesn't tell you:
Webhooks Are Unreliable Payment providers send webhooks to confirm payments. Sometimes these webhooks fail. Network issues, server downtime, whatever.
We always verify payments manually:
// After user returns from payment page
async function verifyPayment(reference: string) {
const response = await fetch(
`https://api.paymentprovider.com/verify/${reference}`,
{
headers: {
Authorization: `Bearer ${process.env.PAYMENT_SECRET_KEY}`
}
}
)
const data = await response.json()
if (data.status === 'success') {
// Update order status
// Send confirmation email
// Whatever your business logic needs
}
}
Never trust the webhook alone. Always verify.
Test with Real Money We test with small transactions. Real money, real transactions. Sandbox mode doesn't catch everything.
Handle Failed Payments Gracefully Payments fail. Cards decline. Banks have issues. Your app needs to handle this:
if (paymentStatus === 'failed') {
return (
<div>
<h2>Payment Failed</h2>
<p>Your payment could not be processed.</p>
<button onClick={retryPayment}>Try Again</button>
<button onClick={useAnotherCard}>Use Another Card</button>
<button onClick={contactSupport}>Contact Support</button>
</div>
)
}
Give users options. Don't just show an error and leave them stuck.
Database Choices
We use PostgreSQL for most projects. Hosted on Railway or Render.
Why PostgreSQL?
- Reliable
- Good documentation
- Works well with Prisma
- Affordable hosting options
Why Not MongoDB? We tried it. For most business applications, relational data makes more sense. Schools have students, classes, fees. E-commerce has products, orders, customers. These are relational.
MongoDB works great for some use cases. Just not most of ours.
Prisma Makes Life Easier
// Define your schema
model User {
id Int @id @default(autoincrement())
email String @unique
name String
orders Order[]
createdAt DateTime @default(now())
}
// Use it in your code
const user = await prisma.user.create({
data: {
email: 'user@example.com',
name: 'John Doe'
}
})
Type-safe database queries. Migrations handled automatically. Worth the learning curve.
Deployment Strategy
We deploy to Vercel for most projects. Sometimes Railway or Render if the client needs more control.
Vercel Pros:
- Dead simple deployment
- Automatic HTTPS
- Good global performance
- Free tier is generous
Vercel Cons:
- Can get expensive at scale
- Less control over server configuration
- Vendor lock-in concerns
Our Deployment Checklist:
- Environment variables set correctly
- Database migrations run
- Test payment integration on production
- Check mobile responsiveness
- Test on slow 3G connection
- Verify email sending works
- Check error logging (we use Sentry)
Authentication Approach
We use NextAuth.js for most projects. Simple, works well with Next.js, supports multiple providers.
// pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'
export default NextAuth({
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
// Your authentication logic
const user = await verifyUser(credentials)
return user
}
})
],
pages: {
signIn: '/login',
error: '/login',
}
})
For business applications, we add Google OAuth. Makes login easier for users.
Error Handling
Production apps break. Internet fails. APIs timeout. Databases go down.
What We Do:
Error Boundaries
'use client'
export default function ErrorBoundary({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-2xl font-bold mb-4">Something went wrong</h2>
<p className="text-gray-600 mb-6">We're working on fixing this.</p>
<button
onClick={reset}
className="bg-blue-600 text-white px-6 py-3 rounded-lg"
>
Try Again
</button>
</div>
)
}
Sentry for Error Tracking We know when errors happen, where they happen, and how often. Sentry sends us alerts. We fix issues before clients complain.
Graceful Degradation If an API fails, show cached data. If images don't load, show placeholders. Don't let one failure break the entire app.
Performance Monitoring
We use Vercel Analytics and Google Analytics. Both free, both useful.
What We Track:
- Page load times
- Core Web Vitals
- User flow through the app
- Where users drop off
- Most visited pages
This data tells us what to optimize next.
Tools We Actually Use
Development:
- VS Code
- Git & GitHub
- Postman for API testing
- Figma for designs
Deployment:
- Vercel (primary)
- Railway (databases)
- Cloudflare (DNS and CDN)
Monitoring:
- Sentry (errors)
- Vercel Analytics (performance)
- Google Analytics (user behavior)
Common Mistakes We Made
Overengineering Early We used to add every feature we thought clients might need. Now we build the minimum viable product first. Add features based on actual usage.
Ignoring Mobile Performance We optimized for desktop first. Big mistake. Mobile is where your users are.
Not Testing Payment Integration Enough We trusted sandbox mode. Then production payments failed. Now we test with real money before launch.
Skipping Error Handling We focused on happy paths. Then production errors happened and users got confused. Now we handle errors from day one.
Advice for Developers
Learn the Fundamentals Next.js is great, but learn JavaScript and React properly first. Understand how the web works. Don't just copy code from tutorials.
Build Real Projects Build something people will actually use. A todo app is fine for learning, but build a real business application. You'll learn more from one real project than ten tutorials.
Test on Slow Internet Use Chrome DevTools to throttle your connection to 3G. If your app works on 3G, it'll work anywhere.
Join Developer Communities Attend developer meetups. Learn from other developers. Share your knowledge.
Charge What You're Worth Don't undervalue your work. Good developers are in demand. If you're building quality applications, charge accordingly.
Resources That Actually Helped Us
- Next.js documentation (actually read it)
- Vercel's YouTube channel (practical examples)
- Josh Comeau's blog (CSS and React)
- Kent C. Dodds' articles (testing and best practices)
- Developer communities on Twitter and Discord
Final Thoughts
Building production Next.js apps has its challenges. Slow internet, mobile-first users, payment integration quirks, and client management.
But it's also rewarding. You're building tools that help businesses operate better. Schools manage students more efficiently. E-commerce stores reach more customers. Businesses automate their operations.
The technical skills matter. But understanding your users and their constraints matters more.
Build for slow internet speeds. Design for mobile phones. Test with real users. Handle errors gracefully. And always, always verify payments manually.
About Raspib Technology
We're a software development company specializing in web applications, mobile apps, and IT consulting services. We've been working with Next.js since version 12 and have built over 20 production applications.
If you're a developer looking to collaborate or a business needing a Next.js application, reach out.
📞 +234 905 162 3555
📧 info@raspibtech.com
🌐 raspibtech.com
RC: 8957098 | DUNS: 669824701
Need Help with Your Project?
Let's discuss how Raspib Technology can help transform your business
Related Articles
Laravel 11: What Changed and Why You Should Care
Laravel 11 is out. Slimmer structure, better performance, and features that actually save time. Here's what matters.
Read more →Laravel 12: The Upgrade You've Been Waiting For
Laravel 12 brings major improvements. Here's what changed and why it matters for your projects.
Read more →Next.js 15: The Features That Actually Matter
Next.js 15 changed a lot. Here's what affects your projects, what breaks, and when to upgrade.
Read more →