Skip to main content

Query Strip Middleware

Query Strip Middleware

This middleware strips query parameters from incoming requests based on a per-route configuration. It is mainly used to prevent sensitive query data from being embedded in cached HTML responses (for example, in __NEXT_DATA__.query), while keeping the browser URL unchanged.

When It Applies

  • It only matches static paths (the part of the URL after the locale prefix).
  • The config key must start with /.
  • Paths are matched exactly (no dynamic URL matching).
  • Locale is detected by the first path segment matching ??-?? (case-insensitive), such as en-id.

Configuration

Each route maps to a cache key array. Only the listed query params are preserved, along with the default cache keys. If the array is empty, all query params are stripped except the preserved keys.

The middleware always preserves cur, __nextDataReq, and currency because they are default cache keys.

A complete example

The flight/fullsearch set three cache keys: 'ap', 'utm_source' and 'findcheapestdate'. Below are the steps to preserve these cache keys.

  1. Add the cache key type in packages/core/next/view-cache-key/types.ts:
export type FlightCSPCacheKey = 'ap' | 'utm_source' | 'findcheapestdate';
export type CSPCacheKey = PaymentCSPCacheKey | FlightCSPCacheKey;
  1. Add the route rule in packages/core/next/view-cache-key/queryCacheKeySetting.ts:
export const FlightQueryCacheKeySetting: {
[pathname: string]: Array<FlightCSPCacheKey>;
} = {
'/flight/fullsearch': ['ap', 'findcheapestdate', 'utm_source'],
};

export const queryCacheKeySetting: {
[pathname: string]: Array<CSPCacheKey>;
} = {
...FlightQueryCacheKeySetting,
...PaymentQueryCacheKeySetting,
};

After this, the query strip middleware will keep only ap, findcheapestdate, utm_source, and the default preserved keys (cur, currency, __nextDataReq) for /flight/fullsearch.

  1. Export FlightQueryCacheKeySetting in packages/core/next/view-cache-key/index.ts
export {
PaymentQueryCacheKeySetting,
FlightQueryCacheKeySetting,
} from './queryCacheKeySetting';
  1. Add Middleware in Express Server

The flight desktop server wires the middleware using the shared cache key config:

import { queryStripMiddleware } from '@traveloka/core/server';
import { FlightQueryCacheKeySetting } from '@traveloka/core/next/view-cache-key';

server.use(queryStripMiddleware(FlightQueryCacheKeySetting));

Example Behavior

Input:

https://www.traveloka.com/en-id/flight/fullsearch?ap=CGK.AMS&dt=28-11-2025.NA&ps=1.0.0&sc=ECONOMY

Config:

{
'/flight/fullsearch': ['ap', 'utm_source', 'findcheapestdate']
}

Output (as received by Next.js server):

https://www.traveloka.com/en-id/flight/fullsearch?ap=CGK.AMS&__cfnrewrite__=1

The middleware appends __cfnrewrite__=1 whenever it strips any query params. This flag is used by the client to detect the rewrite and restore the stripped parameters from the real browser URL.

Note the middleware only changes how Next.js Server receives the URL. The URL in the browser remains unchanged and the query parameters can be retrieved by useCurrentRoute / useNextRouter.

Notes

  • If the matched config key is not found, the middleware does nothing.
  • If a config key does not start with /, it is ignored.
  • The URL in the client browser is kept unchanged. It only affects the __NEXT_DATA__.query object.
  • The removed query keys will be inaccessible in Next.js Server.
  • cur, __nextDataReq, and currency are always preserved.

Client-side Query Restoration

When __cfnrewrite__=1 is present in the query, useNextRouter and useCurrentRoute automatically restore the stripped parameters from the real browser URL (window.location / asPath). The flag is stripped from the final query object so consumers never see it.

The restoration behavior depends on the hydrationSafe option.

hydrationSafe: false (default)

Suitable when the query parameters do not directly affect the SSR-rendered output

  • Restoration happens immediately on the first CSR render — the correct params are available from the very first call to useNextRouter / useCurrentRoute.
  • There is no hydration mismatch concern because these params were never used to produce the server HTML.
// Both are equivalent — hydrationSafe defaults to false
const router = useNextRouter();
const route = useCurrentRoute();

// Explicit:
const router = useNextRouter({ hydrationSafe: false });
const route = useCurrentRoute({ hydrationSafe: false });

hydrationSafe: true

Suitable when the query parameters directly affect the SSR-rendered output — for example, when a component reads a param during render and uses it to determine what to show.

Because the SSR HTML was rendered with the stripped query (without the real params), restoring the full params immediately on the client would cause a hydration mismatch (client output differs from server HTML). To avoid this, hydrationSafe: true defers restoration:

  • Before client is ready (during hydration): returns the SSR-computed route / query, matching the server HTML.
  • After client is ready (post-hydration): returns the restored route / query with the real params from the browser URL, triggering a re-render.
const router = useNextRouter({ hydrationSafe: true });
const route = useCurrentRoute({ hydrationSafe: true });

When to use which:

  • Use hydrationSafe: false (default) if the param is only consumed in useEffect, event handlers, or any code that does not run during SSR/hydration render. Or the usage of the param does not affect HTML render result.
  • Use hydrationSafe: true if the param is read directly during render and its value influences what the component renders on first paint.