veryfront 0.1.53 → 0.1.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/html/styles-builder/tailwind-compiler-cache.d.ts +1 -1
  3. package/esm/src/html/styles-builder/tailwind-compiler-cache.d.ts.map +1 -1
  4. package/esm/src/html/styles-builder/tailwind-compiler-cache.js +8 -4
  5. package/esm/src/html/styles-builder/tailwind-compiler.d.ts +2 -1
  6. package/esm/src/html/styles-builder/tailwind-compiler.d.ts.map +1 -1
  7. package/esm/src/html/styles-builder/tailwind-compiler.js +7 -3
  8. package/esm/src/modules/react-loader/ssr-module-loader/constants.d.ts +1 -1
  9. package/esm/src/modules/react-loader/ssr-module-loader/constants.d.ts.map +1 -1
  10. package/esm/src/modules/react-loader/ssr-module-loader/constants.js +2 -2
  11. package/esm/src/modules/react-loader/ssr-module-loader/ssr-circuit-breaker.d.ts.map +1 -1
  12. package/esm/src/modules/react-loader/ssr-module-loader/ssr-circuit-breaker.js +3 -4
  13. package/esm/src/rendering/orchestrator/pipeline.d.ts +2 -0
  14. package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
  15. package/esm/src/rendering/orchestrator/pipeline.js +33 -7
  16. package/esm/src/server/dev-server/error-overlay/html-template.js +1 -1
  17. package/esm/src/server/handlers/dev/styles-css.handler.d.ts.map +1 -1
  18. package/esm/src/server/handlers/dev/styles-css.handler.js +3 -1
  19. package/esm/src/server/handlers/request/css.handler.d.ts.map +1 -1
  20. package/esm/src/server/handlers/request/css.handler.js +3 -3
  21. package/package.json +1 -1
  22. package/src/deno.js +1 -1
  23. package/src/src/html/styles-builder/tailwind-compiler-cache.ts +8 -3
  24. package/src/src/html/styles-builder/tailwind-compiler.ts +11 -3
  25. package/src/src/modules/react-loader/ssr-module-loader/constants.ts +2 -2
  26. package/src/src/modules/react-loader/ssr-module-loader/ssr-circuit-breaker.ts +3 -4
  27. package/src/src/rendering/orchestrator/pipeline.ts +46 -8
  28. package/src/src/server/dev-server/error-overlay/html-template.ts +1 -1
  29. package/src/src/server/handlers/dev/styles-css.handler.ts +3 -1
  30. package/src/src/server/handlers/request/css.handler.ts +11 -3
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -1,5 +1,5 @@
1
1
  import { compile } from "tailwindcss";
2
- export declare function getCompiler(stylesheet: string): Promise<Awaited<ReturnType<typeof compile>>>;
2
+ export declare function getCompiler(stylesheet: string, projectSlug?: string): Promise<Awaited<ReturnType<typeof compile>>>;
3
3
  export declare function invalidateCompiler(): void;
4
4
  /**
5
5
  * Get compiler cache statistics for monitoring.
@@ -1 +1 @@
1
- {"version":3,"file":"tailwind-compiler-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/html/styles-builder/tailwind-compiler-cache.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA2EtC,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,CA8C9C;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E,CAQA;AAED,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAalD"}
1
+ {"version":3,"file":"tailwind-compiler-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/html/styles-builder/tailwind-compiler-cache.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA2EtC,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,CAkD9C;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E,CAQA;AAED,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAalD"}
@@ -62,14 +62,18 @@ function evictOldestCompiler() {
62
62
  compilerCache.delete(oldestKey);
63
63
  logger.debug("Evicted oldest compiler from cache", { hash: oldestKey });
64
64
  }
65
- export async function getCompiler(stylesheet) {
66
- const hash = hashString(stylesheet);
65
+ export async function getCompiler(stylesheet, projectSlug) {
66
+ // Tailwind v4's compile().build() is stateful — it accumulates candidates
67
+ // across calls. Without per-project isolation, projects sharing the same
68
+ // stylesheet on the shared pool contaminate each other's CSS output.
69
+ const stylesheetHash = hashString(stylesheet);
70
+ const hash = projectSlug ? `${projectSlug}:${stylesheetHash}` : stylesheetHash;
67
71
  const cached = compilerCache.get(hash);
68
72
  if (cached) {
69
- logger.debug("Compiler cache hit", { hash });
73
+ logger.debug("Compiler cache hit", { hash, projectSlug });
70
74
  return cached.compiler;
71
75
  }
72
- logger.debug("Creating new compiler", { hash });
76
+ logger.debug("Creating new compiler", { hash, projectSlug });
73
77
  const tailwindBase = await getTailwindBaseCSS();
74
78
  const pluginCache = new Map();
75
79
  const pluginErrors = new Map();
@@ -11,6 +11,7 @@ export interface GenerateOptions {
11
11
  minify?: boolean;
12
12
  environment?: string;
13
13
  buildMode?: "development" | "production";
14
+ projectSlug?: string;
14
15
  }
15
16
  export interface CSSErrorInfo {
16
17
  title: string;
@@ -32,7 +33,7 @@ export declare function getProjectCSS(projectSlug: string, stylesheet: string |
32
33
  * @param expectedHash - The CSS hash to regenerate
33
34
  * @returns The regenerated CSS if inputs are cached and hash matches, undefined otherwise
34
35
  */
35
- export declare function regenerateCSSByHash(expectedHash: string): Promise<string | undefined>;
36
+ export declare function regenerateCSSByHash(expectedHash: string, projectSlug: string | undefined): Promise<string | undefined>;
36
37
  export declare function generateTailwindCSS(stylesheet: string | undefined, candidates: string[] | Set<string>, options?: GenerateOptions): Promise<TailwindResult>;
37
38
  export declare function formatCSSError(error: Error | string): CSSErrorInfo;
38
39
  //# sourceMappingURL=tailwind-compiler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tailwind-compiler.d.ts","sourceRoot":"","sources":["../../../../src/src/html/styles-builder/tailwind-compiler.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAShC,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;CAC1C;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAmE5D;AAMD;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA2D3F;AAMD,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,UAAU,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAgCzB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,CAGlE"}
1
+ {"version":3,"file":"tailwind-compiler.d.ts","sourceRoot":"","sources":["../../../../src/src/html/styles-builder/tailwind-compiler.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAShC,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAsE5D;AAMD;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA4D7B;AAMD,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,UAAU,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAgCzB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,CAGlE"}
@@ -45,7 +45,10 @@ export async function getProjectCSS(projectSlug, stylesheet, candidates, options
45
45
  }
46
46
  const generationPromise = (async () => {
47
47
  // Generate fresh CSS
48
- const result = await generateTailwindCSS(context.stylesheet, candidates, options);
48
+ const result = await generateTailwindCSS(context.stylesheet, candidates, {
49
+ ...options,
50
+ projectSlug,
51
+ });
49
52
  if (result.error) {
50
53
  const formatted = formatCSSError(result.error);
51
54
  logger.error("Project CSS generation failed", {
@@ -88,7 +91,7 @@ export async function getProjectCSS(projectSlug, stylesheet, candidates, options
88
91
  * @param expectedHash - The CSS hash to regenerate
89
92
  * @returns The regenerated CSS if inputs are cached and hash matches, undefined otherwise
90
93
  */
91
- export async function regenerateCSSByHash(expectedHash) {
94
+ export async function regenerateCSSByHash(expectedHash, projectSlug) {
92
95
  const inFlight = inFlightRegeneration.get(expectedHash);
93
96
  if (inFlight)
94
97
  return await inFlight;
@@ -100,6 +103,7 @@ export async function regenerateCSSByHash(expectedHash) {
100
103
  }
101
104
  const result = await generateTailwindCSS(inputs.stylesheet, inputs.candidates, {
102
105
  minify: true,
106
+ projectSlug,
103
107
  });
104
108
  if (result.error) {
105
109
  logger.warn("CSS regeneration failed", {
@@ -145,7 +149,7 @@ export async function generateTailwindCSS(stylesheet, candidates, options) {
145
149
  return await withSpan(SpanNames.HTML_GENERATE_TAILWIND_CSS, async () => {
146
150
  const css = stylesheet ?? DEFAULT_STYLESHEET;
147
151
  try {
148
- const comp = await getCompiler(css);
152
+ const comp = await getCompiler(css, options?.projectSlug);
149
153
  let output = comp.build(candidateArray);
150
154
  if (options?.minify)
151
155
  output = minifyCSS(output);
@@ -6,7 +6,7 @@ export declare const REDIS_KEY_PREFIX = "veryfront:ssr-module:";
6
6
  /** Get environment-aware Redis TTL for SSR modules */
7
7
  export declare function getSSRModuleRedisTTL(isProduction: boolean): number;
8
8
  export { DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC, DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC };
9
- export declare const CIRCUIT_BREAKER_THRESHOLD = 3;
9
+ export declare const CIRCUIT_BREAKER_THRESHOLD = 25;
10
10
  export declare const CIRCUIT_BREAKER_RESET_MS: number;
11
11
  export declare function getMaxConcurrentTransforms(): number;
12
12
  export declare function getTransformPerProjectLimit(): number;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/constants.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,sCAAsC,EACtC,yCAAyC,EAG1C,MAAM,mCAAmC,CAAC;AAE3C,eAAO,MAAM,4BAA4B,OAAO,CAAC;AACjD,eAAO,MAAM,uBAAuB,QAAqB,CAAC;AAE1D,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD,sDAAsD;AACtD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,OAAO,GAAG,MAAM,CAElE;AAED,OAAO,EAAE,sCAAsC,EAAE,yCAAyC,EAAE,CAAC;AAE7F,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAC3C,eAAO,MAAM,wBAAwB,QAAY,CAAC;AAIlD,wBAAgB,0BAA0B,IAAI,MAAM,CAOnD;AASD,wBAAgB,2BAA2B,IAAI,MAAM,CAQpD;AAED,oEAAoE;AACpE,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD;AAED,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAElD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,oBAAoB,KAAK,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/constants.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,sCAAsC,EACtC,yCAAyC,EAG1C,MAAM,mCAAmC,CAAC;AAE3C,eAAO,MAAM,4BAA4B,OAAO,CAAC;AACjD,eAAO,MAAM,uBAAuB,QAAqB,CAAC;AAE1D,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD,sDAAsD;AACtD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,OAAO,GAAG,MAAM,CAElE;AAED,OAAO,EAAE,sCAAsC,EAAE,yCAAyC,EAAE,CAAC;AAE7F,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAC5C,eAAO,MAAM,wBAAwB,QAAW,CAAC;AAIjD,wBAAgB,0BAA0B,IAAI,MAAM,CAOnD;AASD,wBAAgB,2BAA2B,IAAI,MAAM,CAQpD;AAED,oEAAoE;AACpE,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD;AAED,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAElD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,oBAAoB,KAAK,CAAC"}
@@ -10,8 +10,8 @@ export function getSSRModuleRedisTTL(isProduction) {
10
10
  return getDistributedCacheTTL("ssr-module", isProduction);
11
11
  }
12
12
  export { DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC, DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC };
13
- export const CIRCUIT_BREAKER_THRESHOLD = 3;
14
- export const CIRCUIT_BREAKER_RESET_MS = 60 * 1000;
13
+ export const CIRCUIT_BREAKER_THRESHOLD = 25;
14
+ export const CIRCUIT_BREAKER_RESET_MS = 5 * 1000;
15
15
  // Max concurrent ESM transforms (safety net, not throttle). Set to 0 to disable.
16
16
  let _maxConcurrentTransforms;
17
17
  export function getMaxConcurrentTransforms() {
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-circuit-breaker.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/ssr-circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAC5B;;;;OAIG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IA+BjD;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAOxC"}
1
+ {"version":3,"file":"ssr-circuit-breaker.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/ssr-circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAC5B;;;;OAIG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IA8BjD;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAOxC"}
@@ -31,12 +31,11 @@ export class SSRCircuitBreaker {
31
31
  if (failureRecord.count >= CIRCUIT_BREAKER_THRESHOLD &&
32
32
  timeSinceFailure < CIRCUIT_BREAKER_RESET_MS) {
33
33
  throw toError(createError({
34
- type: "build",
34
+ type: "render",
35
35
  message: `Component ${filePath} is temporarily blocked due to repeated failures. Will retry in ${Math.ceil((CIRCUIT_BREAKER_RESET_MS - timeSinceFailure) / 1000)}s.`,
36
36
  context: {
37
- file: filePath,
38
- phase: "circuit-breaker",
39
- failures: failureRecord.count,
37
+ component: filePath,
38
+ phase: "server",
40
39
  },
41
40
  }));
42
41
  }
@@ -53,6 +53,8 @@ export declare class RenderPipeline {
53
53
  */
54
54
  clearModuleCache(): void;
55
55
  private loadModule;
56
+ private extractRenderedCssHash;
57
+ private resolveCssFromRenderedHtml;
56
58
  /**
57
59
  * Load modules in parallel and return only successfully loaded ones.
58
60
  *
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAwChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,MAAM,EAAE,oBAAoB;IAaxC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;IAIlB;;;;;;;;;OASG;YACW,qBAAqB;IAyDnC;;;OAGG;YACW,mBAAmB;IAqH3B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAgP9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA6KvF;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAetB"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA6ChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,MAAM,EAAE,oBAAoB;IAaxC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,sBAAsB;YAIhB,0BAA0B;IAaxC;;;;;;;;;OASG;YACW,qBAAqB;IAyDnC;;;OAGG;YACW,mBAAmB;IAqH3B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAgP9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA6LvF;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAetB"}
@@ -30,6 +30,7 @@ import { setupSSRGlobals } from "../ssr-globals.js";
30
30
  import { LAYOUT_EXTENSIONS } from "../layouts/types.js";
31
31
  import { withTimeout, withTimeoutThrow } from "../utils/stream-utils.js";
32
32
  import { extractCandidates, generateTailwindCSS } from "../../html/styles-builder/index.js";
33
+ import { getCSSByHashAsync, regenerateCSSByHash, } from "../../html/styles-builder/tailwind-compiler.js";
33
34
  import { createEsmCache, createModuleCache, loadModule } from "./module-loader/index.js";
34
35
  import { getCSSImports, runWithCSSCollector, } from "../../modules/react-loader/css-import-collector.js";
35
36
  // Extracted modules
@@ -39,6 +40,7 @@ import { collectModulesToLoad, DATA_FETCH_TIMEOUT_MS, hasDataFetchingFunction, M
39
40
  const renderPageLog = logger.component("render-page");
40
41
  const renderPipelineLog = logger.component("render-pipeline");
41
42
  const resolvePageDataLog = logger.component("resolve-page-data");
43
+ const RENDERED_CSS_HASH_RE = /href="\/_vf\/css\/([a-z0-9-]{1,16})\.css"/i;
42
44
  // Re-export test helper for backward compatibility
43
45
  export { __injectCssCacheForTests } from "./css-cache.js";
44
46
  export class RenderPipeline {
@@ -68,6 +70,18 @@ export class RenderPipeline {
68
70
  loadModule(filePath) {
69
71
  return loadModule(filePath, this.moduleLoaderConfig);
70
72
  }
73
+ extractRenderedCssHash(html) {
74
+ return html.match(RENDERED_CSS_HASH_RE)?.[1];
75
+ }
76
+ async resolveCssFromRenderedHtml(html, projectSlug) {
77
+ const cssHash = this.extractRenderedCssHash(html);
78
+ if (!cssHash)
79
+ return undefined;
80
+ const cachedCss = await getCSSByHashAsync(cssHash);
81
+ if (cachedCss)
82
+ return cachedCss;
83
+ return await regenerateCSSByHash(cssHash, projectSlug);
84
+ }
71
85
  /**
72
86
  * Load modules in parallel and return only successfully loaded ones.
73
87
  *
@@ -438,15 +452,27 @@ export class RenderPipeline {
438
452
  skipCachePersist: true,
439
453
  }), CSS_SSR_TIMEOUT_MS, `CSS SSR for ${slug}`);
440
454
  if (renderResult?.html) {
441
- const candidates = extractCandidates(renderResult.html);
442
- css = (await generateTailwindCSS(undefined, candidates)).css;
455
+ css = await this.resolveCssFromRenderedHtml(renderResult.html, options?.projectSlug ?? options?.projectId);
456
+ if (css) {
457
+ resolvePageDataLog.debug("Reused SSR CSS for page data", {
458
+ slug,
459
+ cssLength: css.length,
460
+ source: "rendered-html-hash",
461
+ });
462
+ }
463
+ else {
464
+ const candidates = extractCandidates(renderResult.html);
465
+ css = (await generateTailwindCSS(undefined, candidates, {
466
+ projectSlug: options?.projectSlug,
467
+ })).css;
468
+ resolvePageDataLog.debug("Fell back to HTML candidate CSS generation", {
469
+ slug,
470
+ htmlLength: renderResult.html.length,
471
+ cssLength: css?.length || 0,
472
+ });
473
+ }
443
474
  if (css)
444
475
  cachePageCss(cssCacheKey, css);
445
- resolvePageDataLog.debug("Generated and cached CSS", {
446
- slug,
447
- htmlLength: renderResult.html.length,
448
- cssLength: css?.length || 0,
449
- });
450
476
  }
451
477
  }
452
478
  catch (error) {
@@ -178,7 +178,7 @@ export function generateErrorHTML(errorInfo, suggestion) {
178
178
  <html>
179
179
  <head>
180
180
  <meta charset="UTF-8">
181
- <title>Build Error - Veryfront</title>
181
+ <title>${errorType} Error - Veryfront</title>
182
182
  <style>
183
183
  body {
184
184
  margin: 0;
@@ -1 +1 @@
1
- {"version":3,"file":"styles-css.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/dev/styles-css.handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAanG,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,QAAQ,EAAE,eAAe,CAKvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;YAgGjE,cAAc;CAiB7B"}
1
+ {"version":3,"file":"styles-css.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/dev/styles-css.handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAanG,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,QAAQ,EAAE,eAAe,CAKvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;YAkGjE,cAAc;CAiB7B"}
@@ -39,7 +39,9 @@ export class StylesCSSHandler extends BaseHandler {
39
39
  });
40
40
  candidates = new Set();
41
41
  }
42
- const result = await generateTailwindCSS(rawCss, candidates);
42
+ const result = await generateTailwindCSS(rawCss, candidates, {
43
+ projectSlug: ctx.projectSlug,
44
+ });
43
45
  if (result.error) {
44
46
  const formatted = formatCSSError(result.error);
45
47
  logger.error("Tailwind error", {
@@ -1 +1 @@
1
- {"version":3,"file":"css.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/css.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAuBnG,qBAAa,UAAW,SAAQ,WAAW;IACzC,QAAQ,EAAE,eAAe,CAOvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CA6DhF"}
1
+ {"version":3,"file":"css.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/css.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AA0BnG,qBAAa,UAAW,SAAQ,WAAW;IACzC,QAAQ,EAAE,eAAe,CAOvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAkEhF"}
@@ -6,11 +6,11 @@ import { getEnv } from "../../../platform/compat/process.js";
6
6
  import { runWithRequestContext } from "../../../platform/adapters/fs/veryfront/multi-project-adapter.js";
7
7
  /** Pattern to match hashed CSS URLs: /_vf/css/[8-char-hash].css */
8
8
  const CSS_URL_PATTERN = /^\/_vf\/css\/([a-z0-9-]{1,16})\.css$/;
9
- async function getCSSWithJITFallback(cssHash) {
9
+ async function getCSSWithJITFallback(cssHash, projectSlug) {
10
10
  const cached = await getCSSByHashAsync(cssHash);
11
11
  if (cached)
12
12
  return cached;
13
- return regenerateCSSByHash(cssHash);
13
+ return regenerateCSSByHash(cssHash, projectSlug);
14
14
  }
15
15
  export class CSSHandler extends BaseHandler {
16
16
  metadata = {
@@ -35,7 +35,7 @@ export class CSSHandler extends BaseHandler {
35
35
  // null — causing cross-pod cache misses. Wrap the lookup in request context
36
36
  // so the API backend can resolve the token and project.
37
37
  const effectiveToken = ctx.proxyToken || getEnv("VERYFRONT_API_TOKEN") || "";
38
- const lookup = () => runWithCacheKeyContext(cacheCtx, () => getCSSWithJITFallback(cssHash));
38
+ const lookup = () => runWithCacheKeyContext(cacheCtx, () => getCSSWithJITFallback(cssHash, ctx.projectSlug ?? ctx.projectId));
39
39
  const css = ctx.projectSlug
40
40
  ? await runWithRequestContext({
41
41
  projectSlug: ctx.projectSlug,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -87,16 +87,21 @@ function evictOldestCompiler(): void {
87
87
 
88
88
  export async function getCompiler(
89
89
  stylesheet: string,
90
+ projectSlug?: string,
90
91
  ): Promise<Awaited<ReturnType<typeof compile>>> {
91
- const hash = hashString(stylesheet);
92
+ // Tailwind v4's compile().build() is stateful — it accumulates candidates
93
+ // across calls. Without per-project isolation, projects sharing the same
94
+ // stylesheet on the shared pool contaminate each other's CSS output.
95
+ const stylesheetHash = hashString(stylesheet);
96
+ const hash = projectSlug ? `${projectSlug}:${stylesheetHash}` : stylesheetHash;
92
97
 
93
98
  const cached = compilerCache.get(hash);
94
99
  if (cached) {
95
- logger.debug("Compiler cache hit", { hash });
100
+ logger.debug("Compiler cache hit", { hash, projectSlug });
96
101
  return cached.compiler;
97
102
  }
98
103
 
99
- logger.debug("Creating new compiler", { hash });
104
+ logger.debug("Creating new compiler", { hash, projectSlug });
100
105
 
101
106
  const tailwindBase = await getTailwindBaseCSS();
102
107
  const pluginCache = new Map<string, unknown>();
@@ -59,6 +59,7 @@ export interface GenerateOptions {
59
59
  minify?: boolean;
60
60
  environment?: string;
61
61
  buildMode?: "development" | "production";
62
+ projectSlug?: string;
62
63
  }
63
64
 
64
65
  export interface CSSErrorInfo {
@@ -104,7 +105,10 @@ export async function getProjectCSS(
104
105
 
105
106
  const generationPromise = (async () => {
106
107
  // Generate fresh CSS
107
- const result = await generateTailwindCSS(context.stylesheet, candidates, options);
108
+ const result = await generateTailwindCSS(context.stylesheet, candidates, {
109
+ ...options,
110
+ projectSlug,
111
+ });
108
112
 
109
113
  if (result.error) {
110
114
  const formatted = formatCSSError(result.error);
@@ -159,7 +163,10 @@ export async function getProjectCSS(
159
163
  * @param expectedHash - The CSS hash to regenerate
160
164
  * @returns The regenerated CSS if inputs are cached and hash matches, undefined otherwise
161
165
  */
162
- export async function regenerateCSSByHash(expectedHash: string): Promise<string | undefined> {
166
+ export async function regenerateCSSByHash(
167
+ expectedHash: string,
168
+ projectSlug: string | undefined,
169
+ ): Promise<string | undefined> {
163
170
  const inFlight = inFlightRegeneration.get(expectedHash);
164
171
  if (inFlight) return await inFlight;
165
172
 
@@ -174,6 +181,7 @@ export async function regenerateCSSByHash(expectedHash: string): Promise<string
174
181
 
175
182
  const result = await generateTailwindCSS(inputs.stylesheet, inputs.candidates, {
176
183
  minify: true,
184
+ projectSlug,
177
185
  });
178
186
 
179
187
  if (result.error) {
@@ -237,7 +245,7 @@ export async function generateTailwindCSS(
237
245
  const css = stylesheet ?? DEFAULT_STYLESHEET;
238
246
 
239
247
  try {
240
- const comp = await getCompiler(css);
248
+ const comp = await getCompiler(css, options?.projectSlug);
241
249
  let output = comp.build(candidateArray);
242
250
 
243
251
  if (options?.minify) output = minifyCSS(output);
@@ -21,8 +21,8 @@ export function getSSRModuleRedisTTL(isProduction: boolean): number {
21
21
 
22
22
  export { DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC, DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC };
23
23
 
24
- export const CIRCUIT_BREAKER_THRESHOLD = 3;
25
- export const CIRCUIT_BREAKER_RESET_MS = 60 * 1000;
24
+ export const CIRCUIT_BREAKER_THRESHOLD = 25;
25
+ export const CIRCUIT_BREAKER_RESET_MS = 5 * 1000;
26
26
 
27
27
  // Max concurrent ESM transforms (safety net, not throttle). Set to 0 to disable.
28
28
  let _maxConcurrentTransforms: number | undefined;
@@ -37,15 +37,14 @@ export class SSRCircuitBreaker {
37
37
  ) {
38
38
  throw toError(
39
39
  createError({
40
- type: "build",
40
+ type: "render",
41
41
  message:
42
42
  `Component ${filePath} is temporarily blocked due to repeated failures. Will retry in ${
43
43
  Math.ceil((CIRCUIT_BREAKER_RESET_MS - timeSinceFailure) / 1000)
44
44
  }s.`,
45
45
  context: {
46
- file: filePath,
47
- phase: "circuit-breaker",
48
- failures: failureRecord.count,
46
+ component: filePath,
47
+ phase: "server",
49
48
  },
50
49
  }),
51
50
  );
@@ -44,6 +44,10 @@ import { LAYOUT_EXTENSIONS } from "../layouts/types.js";
44
44
  import type { LayoutItem } from "../../types/index.js";
45
45
  import { withTimeout, withTimeoutThrow } from "../utils/stream-utils.js";
46
46
  import { extractCandidates, generateTailwindCSS } from "../../html/styles-builder/index.js";
47
+ import {
48
+ getCSSByHashAsync,
49
+ regenerateCSSByHash,
50
+ } from "../../html/styles-builder/tailwind-compiler.js";
47
51
  import { createEsmCache, createModuleCache, loadModule } from "./module-loader/index.js";
48
52
  import type { ModuleLoaderConfig } from "./module-loader/index.js";
49
53
  import {
@@ -73,6 +77,7 @@ import {
73
77
  const renderPageLog = logger.component("render-page");
74
78
  const renderPipelineLog = logger.component("render-pipeline");
75
79
  const resolvePageDataLog = logger.component("resolve-page-data");
80
+ const RENDERED_CSS_HASH_RE = /href="\/_vf\/css\/([a-z0-9-]{1,16})\.css"/i;
76
81
 
77
82
  // Re-export test helper for backward compatibility
78
83
  export { __injectCssCacheForTests } from "./css-cache.js";
@@ -137,6 +142,23 @@ export class RenderPipeline {
137
142
  return loadModule(filePath, this.moduleLoaderConfig);
138
143
  }
139
144
 
145
+ private extractRenderedCssHash(html: string): string | undefined {
146
+ return html.match(RENDERED_CSS_HASH_RE)?.[1];
147
+ }
148
+
149
+ private async resolveCssFromRenderedHtml(
150
+ html: string,
151
+ projectSlug: string | undefined,
152
+ ): Promise<string | undefined> {
153
+ const cssHash = this.extractRenderedCssHash(html);
154
+ if (!cssHash) return undefined;
155
+
156
+ const cachedCss = await getCSSByHashAsync(cssHash);
157
+ if (cachedCss) return cachedCss;
158
+
159
+ return await regenerateCSSByHash(cssHash, projectSlug);
160
+ }
161
+
140
162
  /**
141
163
  * Load modules in parallel and return only successfully loaded ones.
142
164
  *
@@ -687,15 +709,31 @@ export class RenderPipeline {
687
709
  );
688
710
 
689
711
  if (renderResult?.html) {
690
- const candidates = extractCandidates(renderResult.html);
691
- css = (await generateTailwindCSS(undefined, candidates)).css;
692
- if (css) cachePageCss(cssCacheKey, css);
712
+ css = await this.resolveCssFromRenderedHtml(
713
+ renderResult.html,
714
+ options?.projectSlug ?? options?.projectId,
715
+ );
693
716
 
694
- resolvePageDataLog.debug("Generated and cached CSS", {
695
- slug,
696
- htmlLength: renderResult.html.length,
697
- cssLength: css?.length || 0,
698
- });
717
+ if (css) {
718
+ resolvePageDataLog.debug("Reused SSR CSS for page data", {
719
+ slug,
720
+ cssLength: css.length,
721
+ source: "rendered-html-hash",
722
+ });
723
+ } else {
724
+ const candidates = extractCandidates(renderResult.html);
725
+ css = (await generateTailwindCSS(undefined, candidates, {
726
+ projectSlug: options?.projectSlug,
727
+ })).css;
728
+
729
+ resolvePageDataLog.debug("Fell back to HTML candidate CSS generation", {
730
+ slug,
731
+ htmlLength: renderResult.html.length,
732
+ cssLength: css?.length || 0,
733
+ });
734
+ }
735
+
736
+ if (css) cachePageCss(cssCacheKey, css);
699
737
  }
700
738
  } catch (error) {
701
739
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -187,7 +187,7 @@ export function generateErrorHTML(errorInfo: ErrorInfo, suggestion?: string): st
187
187
  <html>
188
188
  <head>
189
189
  <meta charset="UTF-8">
190
- <title>Build Error - Veryfront</title>
190
+ <title>${errorType} Error - Veryfront</title>
191
191
  <style>
192
192
  body {
193
193
  margin: 0;
@@ -54,7 +54,9 @@ export class StylesCSSHandler extends BaseHandler {
54
54
  });
55
55
  candidates = new Set<string>();
56
56
  }
57
- const result = await generateTailwindCSS(rawCss, candidates);
57
+ const result = await generateTailwindCSS(rawCss, candidates, {
58
+ projectSlug: ctx.projectSlug,
59
+ });
58
60
 
59
61
  if (result.error) {
60
62
  const formatted = formatCSSError(result.error);
@@ -16,11 +16,14 @@ import { runWithRequestContext } from "../../../platform/adapters/fs/veryfront/m
16
16
  /** Pattern to match hashed CSS URLs: /_vf/css/[8-char-hash].css */
17
17
  const CSS_URL_PATTERN = /^\/_vf\/css\/([a-z0-9-]{1,16})\.css$/;
18
18
 
19
- async function getCSSWithJITFallback(cssHash: string): Promise<string | undefined> {
19
+ async function getCSSWithJITFallback(
20
+ cssHash: string,
21
+ projectSlug: string | undefined,
22
+ ): Promise<string | undefined> {
20
23
  const cached = await getCSSByHashAsync(cssHash);
21
24
  if (cached) return cached;
22
25
 
23
- return regenerateCSSByHash(cssHash);
26
+ return regenerateCSSByHash(cssHash, projectSlug);
24
27
  }
25
28
 
26
29
  export class CSSHandler extends BaseHandler {
@@ -48,7 +51,12 @@ export class CSSHandler extends BaseHandler {
48
51
  // null — causing cross-pod cache misses. Wrap the lookup in request context
49
52
  // so the API backend can resolve the token and project.
50
53
  const effectiveToken = ctx.proxyToken || getEnv("VERYFRONT_API_TOKEN") || "";
51
- const lookup = () => runWithCacheKeyContext(cacheCtx, () => getCSSWithJITFallback(cssHash));
54
+ const lookup = () =>
55
+ runWithCacheKeyContext(cacheCtx, () =>
56
+ getCSSWithJITFallback(
57
+ cssHash,
58
+ ctx.projectSlug ?? ctx.projectId,
59
+ ));
52
60
 
53
61
  const css = ctx.projectSlug
54
62
  ? await runWithRequestContext(