Lessons from the Trenches: Building a Production-Grade App with Next.js and Node.js Microservices
A few months ago, I worked on a project that involved building a full-stack, production-grade application using:
Next.js (App Router) on the frontend
Node.js (Express) microservices on the backend
Modern stack. Familiar tools. We were confident going in.
But like many real-world systems, things that sounded simple on paper ended up demanding a deeper level of design, debugging, and coordination. This post isn’t a tutorial — it’s a reflection of the real-world challenges, what we learned, and how we solved them.
🔗 Frontend–Backend Integration: Looks Easy, Until It Isn't
One of the earliest hurdles was getting seamless communication between frontend and backend. We faced issues like:
CORS misconfigurations in dev and staging
Inconsistent API endpoints across environments
Proxy behaviors that differed between local and cloud
We started using shared TypeScript types across the frontend and backend via a monorepo, along with Zod for runtime validation. This ensured that:
API inputs/outputs stayed in sync
Type mismatches were caught early
Changes didn’t accidentally break contracts
It was extra effort, but paid off by reducing bugs from "invisible" backend changes.
🌐 HTTPS and mTLS: Security Beyond the Basics
Enforcing HTTPS locally with self-signed certificates turned out to be more annoying than expected — especially when browsers threw or blocked requests due to security policies.
But things got more serious when we introduced mTLS (mutual TLS) between microservices.
Why mTLS?
We wanted bidirectional authentication between services
Each service had to prove its identity, not just the client
What it involved:
Setting up a lightweight PKI system
Distributing trusted CA certs across environments
Automating certificate rotation and renewal using scripts
This added security and resilience, but brought complexity that required dedicated setup and ops support.
🛑 Resilience: Designing for Partial Failures
Some backend services weren’t always available — and the frontend was initially unprepared.
Our early versions would crash or show blank screens when a service went down. That’s when we realized: frontend resilience matters just as much as backend availability.
We addressed it by:
Wrapping critical API calls with retry logic (using and backoff strategies)
Adding fallback UI states for missing data
Introducing “read-only” modes for degraded functionality
This allowed the app to degrade gracefully, not catastrophically.
🐢 Performance: How SSR Can Hurt If You’re Not Careful
Next.js SSR (server-side rendering) is powerful, but it’s easy to misuse. Our performance issues came from:
Overfetching data in
Heavy synchronous processing during rendering
Bundling large utility libraries unnecessarily
Fixes that helped:
Dynamic imports for expensive or rarely-used components
Refactoring SSR to fetch only what’s needed for the view
Auditing and optimizing stateful components to avoid unnecessary rerenders
Our Core Web Vitals (especially TTFB and FCP) improved significantly after these changes.
🔁 State Management: Clean Data Flow Matters
Our state management strategy was initially all over the place — context everywhere, some Redux legacy, prop drilling, and bugs caused by hydration mismatches in SSR.
Eventually, we standardized on:
Zustand for local/global state (lightweight, no boilerplate)
React Query for API data (automatic caching, retries, and pagination)
This gave us:
Cleaner data flows
Separation of concerns between server state and client state
Far fewer bugs due to hydration issues
Bonus: React Query made it easy to implement background refreshes and stale cache invalidation.
📦 Shared Code: A Double-Edged Sword
We used a monorepo to share:
API types
Validation logic
Utility functions
It worked… until it didn’t.
We ran into:
Circular dependencies
Node-only imports leaking into frontend bundles
Weird build issues caused by mixing ESM and CommonJS
Eventually, we modularized shared logic properly and adopted TypeScript Project References. This kept boundaries clean and builds fast.
Lesson: shared code needs clear ownership and structure, or it becomes technical debt quickly.
🔐 Tokens, SSR & Middleware: Authentication Nightmares
Handling tokens is already tricky — throw in SSR and it gets worse.
We faced:
Expired tokens during SSR that returned stale content
Header leaks in server-rendered responses
Inconsistent cookie parsing across frontend middleware and backend APIs
Fixes:
Built a unified auth middleware (with a shared token handler)
Used httpOnly cookies with refresh logic
Implemented silent token renewal on the client side
This brought us close to a seamless auth experience without compromising on security.
📉 Observability: You Can’t Debug What You Can’t See
SSR issues in production were hard to debug — we couldn’t always replicate them locally.
What saved us:
Sentry for frontend + backend error tracking
Structured logging (JSON logs with correlation IDs across services)
Frontend performance monitoring (tracking slow routes and errors in real time)
With this setup, we could trace bugs across the stack — from browser event to backend log.
🚀 Deployments: Coordination Is Half the Battle
Frontend was deployed on Vercel, backend microservices on AWS ECS, with routing managed by Cloudflare.
Coordination challenges included:
Frontend being live before backend deploy completed
SSL cert mismatches across domains
Rollbacks on one side breaking the other
What helped:
Using feature flags to decouple deploys from releases
Adding health checks for backend APIs
Automating version sync during CI/CD pipelines
🧠 Key Takeaway: Fullstack ≠ Just Code on Both Ends
What I learned is that being “fullstack” doesn’t mean just writing both backend and frontend code.
It means thinking about:
Contracts and validation
Security and observability
Performance tuning
Resilience and degradation
Deployment strategy and infrastructure
This project reminded me that real engineering happens in the gray areas between layers — where design, infrastructure, and code converge.
If you’re working on or have shipped systems like this — I’d love to hear how you handled similar challenges.
#Nextjs #Nodejs #TypeScript #ExpressJS #React #Zustand #ReactQuery #SSR #CSR #DevOps #mTLS #FullstackEngineering #Microservices #DeveloperExperience #WebDev #RealWorldEngineering