← Back to Developer Blog
💻 DeveloperJanuary 18, 20249 min read

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.

By Raspib Technology

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:

  1. Environment variables set correctly
  2. Database migrations run
  3. Test payment integration on production
  4. Check mobile responsiveness
  5. Test on slow 3G connection
  6. Verify email sending works
  7. 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

Building Production Next.js Apps - Real-World Lessons