Skip to content

Commit 2c82c60

Browse files
committed
fix(middleware): do some stuff to avoid weird bugs on Vercel
fix: avoid Promise.all. - await manifest, cspbuilder and config and run finalize sequentially fix: avoid module-level cache variable for global cache - cache within memoize function scope - on Vercel, sometimes a manifest of a different deployment got inserted!
1 parent 3effac2 commit 2c82c60

File tree

6 files changed

+38
-49
lines changed

6 files changed

+38
-49
lines changed

packages/next-safe-middleware/src/middleware/compose/cache.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,30 @@
11
import type { ChainFinalizer, MiddlewareChainContext } from "./types";
22

3-
export const memoizeInChainCache =
4-
<Args extends any[], T>(
5-
key: string,
6-
f: (ctx: MiddlewareChainContext, ...args: Args) => T | Promise<T>
7-
) =>
8-
(ctx: MiddlewareChainContext) =>
9-
async (...args: Args) => {
3+
export const memoizeInChain =
4+
<Args extends any[], T>(key: string, f: (...args: Args) => T | Promise<T>) =>
5+
(...args: Args) =>
6+
async (ctx: MiddlewareChainContext) => {
107
const cached = ctx.cache.get(key) as T;
118
if (cached) {
129
return cached;
1310
}
14-
const fetched = await f(ctx, ...args);
11+
const fetched = await f(...args);
1512
ctx.cache.set(key, fetched);
16-
return fetched;
13+
return ctx.cache.get(key) as T;
1714
};
1815

19-
const globalCache = {};
20-
21-
export const memoizeInGlobalCache =
22-
<Args extends any[], T>(key: string, f: (...args: Args) => T | Promise<T>) =>
23-
async (...args: Parameters<typeof f>) => {
24-
const cached = globalCache[key] as T;
25-
if (cached) {
26-
return cached;
16+
export const memoize = <Args extends any[], T>(
17+
f: (...args: Args) => T | Promise<T>
18+
) => {
19+
let memoized: T;
20+
return async (...args: Parameters<typeof f>) => {
21+
if (memoized) {
22+
return memoized;
2723
}
28-
const fetched = await f(...args);
29-
globalCache[key] = fetched;
30-
return fetched;
24+
memoized = await f(...args);
25+
return memoized;
3126
};
27+
};
3228

3329
export const memoizeResponseHeader = <T>(
3430
header: string,

packages/next-safe-middleware/src/middleware/nextSafe.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,9 @@ const nextSafe = _nextSafe as unknown as typeof _nextSafe.nextSafe;
9999

100100
const _nextSafeMiddleware: MiddlewareBuilder<NextSafeCfg> = (cfg) =>
101101
chainableMiddleware(async (req, evt, ctx) => {
102-
const [cspBuilder, config] = await Promise.all([
103-
cachedCspBuilder(ctx),
104-
unpackConfig(cfg, req, evt, ctx),
105-
]);
102+
const cspBuilder = await cachedCspBuilder(ctx);
103+
const config = await unpackConfig(cfg, req, evt, ctx);
104+
106105
const { disableCsp, userAgent, ...nextSafeCfg } = config;
107106
const isNoCspSecurityHeader = (header) =>
108107
!header.key.toLowerCase().includes(CSP_HEADER) &&

packages/next-safe-middleware/src/middleware/reporting.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ const withBasePath = (reportTo: ReportTo[], basePath?: string) => {
9999

100100
const _reporting: MiddlewareBuilder<ReportingCfg> = (cfg) =>
101101
chainableMiddleware(async (req, evt, ctx) => {
102-
const [config] = await Promise.all([unpackConfig(cfg, req, evt, ctx)]);
102+
const config = await unpackConfig(cfg, req, evt, ctx);
103103
const { reportTo = [], csp: cspCfg } = config;
104104

105105
const { basePath } = req.nextUrl;

packages/next-safe-middleware/src/middleware/strictDynamic.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,9 @@ export type StrictDynamicCfg = {
8080

8181
const _strictDynamic: MiddlewareBuilder<StrictDynamicCfg> = (cfg) =>
8282
chainableMiddleware(async (req, evt, ctx) => {
83-
const [cspManifest, cspBuilder, config] = await Promise.all([
84-
cachedCspManifest(req),
85-
cachedCspBuilder(ctx),
86-
unpackConfig(cfg, req, evt, ctx),
87-
]);
83+
const cspManifest = await cachedCspManifest(req)
84+
const cspBuilder = await cachedCspBuilder(ctx)
85+
const config = await unpackConfig(cfg, req, evt, ctx)
8886

8987
const {
9088
fallbackScriptSrc,

packages/next-safe-middleware/src/middleware/strictInlineStyles.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ export type StrictInlineStylesCfg = {};
77

88
const _strictInlineStyles: MiddlewareBuilder<StrictInlineStylesCfg> = (cfg) =>
99
chainableMiddleware(async (req, evt, ctx) => {
10-
const [cspManifest, cspBuilder] = await Promise.all([
11-
cachedCspManifest(req),
12-
cachedCspBuilder(ctx),
13-
]);
10+
const cspManifest = await cachedCspManifest(req);
11+
const cspBuilder = await cachedCspBuilder(ctx);
12+
1413
if (!cspManifest) {
1514
return;
1615
}

packages/next-safe-middleware/src/middleware/utils.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
CSP_MANIFEST_FILENAME,
1010
} from "../constants";
1111
import type { CspManifest } from "../types";
12-
import { memoizeInChainCache, memoizeInGlobalCache } from "./compose";
12+
import { memoize, memoizeInChain } from "./compose";
13+
1314

1415
const cspBuilderFromCtx = (ctx: MiddlewareChainContext): CspBuilder => {
1516
const headers = ctx.res.get().headers;
@@ -26,22 +27,21 @@ const cspBuilderFromCtx = (ctx: MiddlewareChainContext): CspBuilder => {
2627
return new CspBuilder();
2728
};
2829

29-
const memoizedCspBuilder = memoizeInChainCache(
30-
"csp-builder",
31-
cspBuilderFromCtx
32-
);
30+
const memoizedCspBuilder = memoizeInChain("csp-builder", cspBuilderFromCtx);
3331

3432
const builderToResponse: ChainFinalizer = async (_req, _evt, ctx) => {
35-
const builder = await memoizedCspBuilder(ctx)();
36-
const headers = ctx.res.get().headers;
37-
headers.delete(CSP_HEADER);
38-
headers.delete(CSP_HEADER_REPORT_ONLY);
39-
headers.set(...builder.toHeaderKeyValue());
33+
const builder = ctx.cache.get("csp-builder") as CspBuilder;
34+
if (!builder.isEmpty()) {
35+
const headers = ctx.res.get().headers;
36+
headers.delete(CSP_HEADER);
37+
headers.delete(CSP_HEADER_REPORT_ONLY);
38+
headers.set(...builder.toHeaderKeyValue());
39+
}
4040
};
4141

4242
export const cachedCspBuilder = async (ctx: MiddlewareChainContext) => {
4343
ctx.finalize.addCallback(builderToResponse);
44-
return memoizedCspBuilder(ctx)();
44+
return memoizedCspBuilder(ctx)(ctx);
4545
};
4646

4747
const fetchCspManifest = async (req: NextRequest): Promise<CspManifest> => {
@@ -71,7 +71,4 @@ const fetchCspManifestWithRetry = (
7171
{ retries }
7272
);
7373

74-
export const cachedCspManifest = memoizeInGlobalCache(
75-
"csp-manifest",
76-
fetchCspManifestWithRetry
77-
);
74+
export const cachedCspManifest = memoize(fetchCspManifestWithRetry);

0 commit comments

Comments
 (0)