We had one of those bugs that looked small from the outside, but exposed a much deeper architecture lesson.
In our portal web application, top-level routes worked perfectly:
/registrations/messages/evaluations
But the moment users clicked into deeper routes like:
/registrations/available/57/messages/123
...the entire app reloaded.
The sidebar remounted.
State was lost.
The UX felt broken.
What made this incident especially valuable was the contrast:
- Locally, everything worked.
- In production, it failed.
That gap turned a frustrating bug into a really useful lesson about how Next.js static export actually behaves on Azure.
The Setup
Our app was a Next.js App Router application deployed to Azure Static Web Apps using static export mode.
That meant:
- No Next.js server runtime in production
- No SSR at request time
- No middleware execution at request time
- Everything served from generated static files
We chose this model intentionally for simplicity, cost, and deployment speed.
So this was not full server-mode Next.js.
It was closer to a highly structured SPA that benefits from Next.js routing, layouts, and build output.
The Symptom
From the user's perspective:
- Clicking into detail pages caused a full page refresh
- Shared layout was not preserved
- Sidebar reset
- App state disappeared
- Navigation felt unstable
From the engineering perspective, the real issue was this:
Soft navigation was failing.
The Next.js router was falling back to hard browser navigation.
That distinction ended up being the key.
What We Assumed First
At first, this looked like a classic static hosting issue:
"Maybe Azure Static Web Apps cannot resolve dynamic routes."
That was the obvious explanation.
But it was not fully correct.
Hard deep-linking often worked because rewrite shells were already in place.
The real issue was more subtle:
- Hard navigation often worked
- Soft navigation failed
That completely changed the debugging direction.
The Root Cause
Next.js App Router in static export mode does not only rely on generated HTML files.
For dynamic routes, it also depends on route payload files used during client-side transitions.
In simple terms:
- Hard navigation needs an HTML shell
- Soft navigation needs route payload data
Our Azure rewrite rule for dynamic routes was too greedy.
For example:
/registrations/* → /registrations/static_export.html
At first glance, that looked reasonable.
But the wildcard was also catching requests meant for route payload files.
So during soft navigation, when Next.js requested payload data, Azure returned HTML instead.
The router expected payload data.
It received HTML.
It could not parse the transition response.
So it fell back to full browser navigation.
That is why the entire app reloaded.
The Fix
We solved it in two layers.
1. Correct routing behavior for payload requests
We added rewrite rules so payload requests resolved to payload files, while normal page requests continued resolving to HTML shells.
2. Remove manual maintenance risk
We stopped manually editing staticwebapp.config every time a new dynamic route was added.
Instead, we generated the rewrite rules automatically from the build output.
The generator now:
- Scans exported output for dynamic route artifacts
- Produces both HTML and payload rewrite rules
- Orders rules with the correct specificity
- Writes the final
staticwebapp.configinto the deployment output
That turned a fragile manual config problem into a deterministic build artifact.
Why This Worked
The issue was not:
"Dynamic routes are impossible on static hosting."
The real issue was:
"Runtime payload requests were being rewritten incorrectly."
Once the rewrite rules matched what Next.js App Router actually requests during client-side transitions, the experience became stable again.
No full remount.
No sidebar reset.
No UX jump.
Other Valid Solutions
There is no single best architecture here. There are tradeoffs.
Option A: Stay with static export and generate rewrites
This is what we chose.
Best when:
- SEO is not the main priority
- The app behaves more like a portal or dashboard
- You want predictable static hosting
- Data fetching can happen client-side
- You want lower hosting complexity
Pros:
- Fast deployments
- Lower cost
- No server lifecycle management
- Simple hosting model
Cons:
- You must understand route shells and payload files
- No request-time SSR
- No runtime middleware
- More responsibility on rewrite correctness
Option B: Move to full Next.js server or hybrid hosting
Best when:
- You need SSR
- You need ISR
- You need middleware at request time
- SEO and request-time personalization are important
- You want native Next.js runtime behavior
Pros:
- Full Next.js runtime capabilities
- Fewer static rewrite workarounds
- Better fit for dynamic rendering
Cons:
- More operational complexity
- Higher hosting and runtime considerations
- Platform behavior matters a lot
Option C: Run standalone Next.js on Azure App Service or Container Apps
Best when:
- You want full server runtime control in Azure
- You need predictable feature parity
- Your app depends heavily on SSR or other server behavior
Pros:
- Full server feature set
- Better runtime control
- More flexibility
Cons:
- More moving parts
- More DevOps ownership
- More monitoring and scaling responsibility
Biggest Lesson
Static export is not less engineering.
It is different engineering.
If you choose Next.js App Router with static export:
- Treat rewrite rules as production-critical infrastructure
- Understand hard navigation vs soft navigation
- Understand HTML shells vs route payload files
- Automate config generation from build artifacts
- Do not rely on manual route mapping at scale
Practical Checklist
For teams using Next.js static export on Azure Static Web Apps:
- [ ] Test deep links for every dynamic route
- [ ] Test soft navigation from list pages to detail pages
- [ ] Inspect network responses during client transitions
- [ ] Confirm payload requests are not returning HTML
- [ ] Keep rewrite rule order intentional
- [ ] Generate
staticwebapp.configfrom build output - [ ] Add CI checks for missing route artifacts
- [ ] Test production-like builds early
Closing Thought
This bug looked like a simple route refresh issue.
But in reality, it was a contract mismatch between:
- Next.js App Router export internals
- Azure Static Web Apps rewrite behavior
- Our deployment assumptions
Once those three were aligned, the fix became stable, scalable, and future-proof.
Sometimes the hardest frontend bugs are not inside the component.
They live between build output, hosting rules, and runtime navigation.
Top comments (0)