React Forum
Blog & Guide Responses
Sep 06, 2022 · 04:00 PM

[Deep Dive #22] Middleware Pacts - discussion

Started by Mina Calder
blogmiddlewarepactsroutingguardrailssignals

We have a growing number of cross-cutting behaviors (auth checks, analytics, feature gating) and it's starting to feel like everything is handled in a different place.

Read the post

Do you centralize cross-cutting logic in a middleware layer, or keep it in route shells and routines? How do you keep middleware behavior observable (signals) so it's not just invisible side effects? What makes a middleware pact stable enough to be treated as contract, not convenience?

Comments (10)

Back to latest
Maya Henson
Sep 06, 2022 · 04:05 PM

If middleware isn't observable, it's going to become a place where bugs hide. I'm skeptical by default.

Dmitri Kline
Sep 06, 2022 · 04:14 PM

We like middleware as a pact only when it's explicit and has signals.

If it changes navigation posture, it must write a signal. If it changes fetch posture, it must write a signal.

Related: Security and Safety and Navigation Routines (Deep Dive #4).

Salma Qureshi
Sep 06, 2022 · 04:26 PM

We keep middleware boring: it reads route intent, writes posture, and writes one signal.

ts
function authMiddleware(shell: any) {
  const user = shell.read('user');
  const ok = Boolean(user);
  shell.write('authLane', ok ? 'user' : 'anon');
  shell.write('guardSignal', { kind: ok ? 'ok' : 'auth', action: ok ? 'allow' : 'redirect' });
}
Hana Saito
Sep 06, 2022 · 04:35 PM

Counterpoint: middleware can become a hidden dependency if features start relying on it without knowing.

We require every route to declare which pacts apply. No implicit global middleware.

Mina Calder
Sep 06, 2022 · 04:40 PM

That's a good rule. Implicit global behavior is exactly what we want to avoid.

Declaring pacts per route also makes it clearer when a pact change is a contract change.

Hana Saito
Sep 06, 2022 · 04:43 PM

Yep. Ownership plus visibility.

Keira Santos
Sep 06, 2022 · 04:48 PM

We used to put analytics everywhere. Moving it into a pact helped, but only once we made it idempotent and wrote a signal for sent vs skipped.

Theo Bennett
Sep 06, 2022 · 04:56 PM

If you can't test the pact with route-flow tests, it will drift. Signals make it cheap to test without importing internals.

tsx
const route = useRouteState({ pacts: ['auth', 'analytics'] as const });
const doc = useRouteDoc('orders');
return <div data-pacts={route.read('pacts').join(',')} data-auth-lane={doc.read('authLane')} />;
Amina Farouk
Sep 06, 2022 · 05:10 PM

We had a bug where a pact changed cache posture and suddenly a screen started refetching constantly.

Once cache lanes were visible as signals, the regression was obvious immediately.

Jules Kwon
Sep 06, 2022 · 05:21 PM

The best pact is one you can delete. Keep it small and typed.

Inez Park
Sep 06, 2022 · 05:36 PM

Pacts are basically APIs. If you don't version them and render signals, you'll break routes in subtle ways.

Benji Rios
Sep 06, 2022 · 05:49 PM

Middleware isn't bad. Invisible middleware is bad.