Home / Articles

Next.js 16 Upgrade Traps: Parallel Route Defaults and middleware to proxy

Learn why Next.js 16 parallel route slots need a default.js, how to review a middleware.ts to proxy.ts move safely, and what to test before calling the upgrade done.

Publisher: REACTAPP.TOP6 min read1342 words

This article is published in English.

Sources: Parallel Routes and proxy.ts in Next.js 16: The Routing Changes That Catch Teams Off Guard

Most Next.js 16 upgrade effort goes into the async params and searchParams change, because it breaks loudly and everywhere. Two quieter routing changes deserve just as much care: parallel route slots now need an explicit default.js, and request interception is moving from middleware.ts to a new proxy.ts convention. Neither breaks the pages you click through every day, which is exactly why they slip past testing. This guide explains both changes, why they surface late, and gives you an audit checklist to run before the upgrade ships.

Parallel route slots need a default.js

Parallel routes use folders prefixed with @ to define named slots that a layout renders side by side. Dashboards are the classic case: a main content area next to a persistent activity feed or notifications panel, each with its own navigation state.

In Next.js 16 every slot is expected to have a default.js file. That file is what Next.js renders for a slot when the current URL has no more specific match inside it. Without one, a navigation that lands on such a combination returns a 404 rather than falling back gracefully, and according to the Next.js 16 upgrade notes the build itself may now refuse to proceed when a slot lacks a default, so confirm the exact behavior for your version. A dashboard with a feed slot looks like this:

app/
  dashboard/
    @feed/
      page.tsx       ← the "active" state for this slot
      default.js      ← required fallback when no more-specific match exists
    layout.tsx
    page.tsx

The fallback rarely needs to do much. Rendering null hides the slot, and an empty-state component works when you want the panel to stay visible:

// app/dashboard/@feed/default.js
export default function Default() {
  return null;
}

Why the missing default shows up late

During development you mostly navigate between the states you built and tested, and those all match something in every slot. The problem appears when a user reaches a combination nobody tried: a deep link, a page refresh on a nested URL, or a route that exists for the main content but not for the feed. A full page load cannot reuse a slot's previous state, so Next.js needs default.js to know what to render. In practice this means the 404 is often reported by a confused customer in production rather than caught in development. Adding the file for every slot, even when you think it will never be used, is cheap insurance.

From middleware.ts to proxy.ts

The second change concerns request-level interception. Next.js 16 introduces proxy.ts as the convention going forward for logic that previously lived in middleware.ts, and the old file name is being phased out. At the time of writing, the migration involves renaming the file and its exported function, and the proxy is intended to run on the Node.js runtime; check the current documentation for the details and the official codemod that performs the mechanical part.

If your middleware is trivial, the move is quick. If it does anything substantial, such as authentication checks, resolving tenants from subdomains, redirects or header rewriting, treat it as a code change that needs review rather than a file rename.

Review function by function, not by rename

A blind find-and-replace from middleware.ts to proxy.ts assumes every piece of logic behaves identically under the new convention. Instead, walk through what the file actually does and verify each responsibility separately:

  • which requests the matcher configuration covers, and whether any paths were added or dropped
  • how authentication decisions are made and what happens when a session is missing or expired
  • how tenant identifiers are extracted from hostnames or headers and passed downstream
  • which redirects and rewrites fire, and in what order
  • which headers are read, added or removed

This layer is where a subtle difference in behavior becomes a security bug rather than a cosmetic one. A missed matcher entry can leave a route unauthenticated; a changed header can break tenant isolation. That is why it deserves a deliberate review pass and end-to-end tests, not visual spot checks.

Two unrelated changes with the same failure pattern

Parallel route defaults and the proxy migration have nothing to do with each other technically. Teams tend to discover them in the same upgrade pass because they share a pattern: both mostly work in development and break in specific production scenarios that are hard to reproduce.

That pattern is worth naming. Changes that break every page load get caught within minutes. The risky ones only surface on unusual navigation paths or particular request shapes, so a normal manual click-through never exercises them. Your testing plan should target those paths on purpose.

An audit checklist for the upgrade

Before declaring a Next.js 16 upgrade finished, verify the following:

  1. Every parallel route slot has a default.js. Search the app/ directory for folders starting with @ and check each one.
  2. The middleware logic has been reviewed against the proxy.ts convention one responsibility at a time, not renamed and assumed identical.
  3. Authentication and tenant resolution have been tested end to end after the migration, including negative cases such as a missing session or an unknown subdomain.
  4. Unusual navigation paths, such as deep links, hard refreshes on nested URLs and routes a normal click-through would never reach, have been exercised deliberately, since that is where parallel route 404s hide.

Why multi-tenant dashboards carry the most risk

SaaS dashboards often combine both features: parallel routes for panels, and middleware that maps a subdomain to a tenant before any page renders. That puts tenant resolution directly in the code most affected by the proxy transition.

A mistake here rarely fails loudly. Instead of an error page, you get reports that one tenant briefly saw another tenant's data, which is about as serious an incident as a SaaS product can have. Defense in depth helps: even when the proxy resolves the tenant, data access in server components and route handlers should verify tenant ownership again rather than trusting a header set upstream. Tag-based caching needs the same care, since cache keys must include the tenant; the article on Next.js 16 use cache and tag-based revalidation covers that side.

Common questions

Does skipping default.js fail silently?

Not entirely. You get a 404, or a build error in versions that enforce the rule, but only for the navigation paths that reach the missing fallback. Because the error appears far from the slot that caused it, it is easy to misread as a general routing bug unless you know about this change.

Can you keep using middleware.ts for now?

Check the Next.js documentation for your exact minor version, because support for the old convention is evolving. Given how often authentication lives in this file, postponing the migration indefinitely is riskier than deferring most other upgrade tasks, and doing it early gives you time for a proper review.

How do you find slots without a default.js quickly?

Search for folders under app/ whose names start with @ and list those that do not contain a default.js (or default.tsx) file. A single pass with your editor's file search or a short shell command finds every gap.

Wrapping up

The Next.js 16 changes most likely to hurt are not the ones that break your happy path. Parallel route slots without a default and a hastily renamed proxy both pass casual testing and fail on edge cases, sometimes with security consequences. Add a default to every slot, review interception logic responsibility by responsibility, and budget real testing time for the routes and requests a normal walkthrough never touches.