qlara 0.1.7 → 0.1.9

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.
package/dist/aws.cjs CHANGED
@@ -562,7 +562,7 @@ async function bundleEdgeHandler(config) {
562
562
  });
563
563
  return createZip(outfile, "edge-handler.js");
564
564
  }
565
- async function bundleRenderer(routeFile, cacheTtl = 3600) {
565
+ async function bundleRenderer(routeFile, cacheTtl = 3600, framework) {
566
566
  (0, import_node_fs2.mkdirSync)(BUNDLE_DIR, { recursive: true });
567
567
  const outfile = (0, import_node_path2.join)(BUNDLE_DIR, "renderer.js");
568
568
  const alias = {};
@@ -584,7 +584,8 @@ async function bundleRenderer(routeFile, cacheTtl = 3600) {
584
584
  minify: true,
585
585
  alias,
586
586
  define: {
587
- __QLARA_CACHE_TTL__: String(cacheTtl)
587
+ __QLARA_CACHE_TTL__: String(cacheTtl),
588
+ __QLARA_FRAMEWORK__: JSON.stringify(framework || "")
588
589
  },
589
590
  external: []
590
591
  });
@@ -764,8 +765,9 @@ async function updateCloudFrontEdgeVersion(cf, distributionId, newVersionArn) {
764
765
  const lambdaAssociations = config.DefaultCacheBehavior?.LambdaFunctionAssociations;
765
766
  if (lambdaAssociations?.Items) {
766
767
  for (const assoc of lambdaAssociations.Items) {
767
- if (assoc.EventType === "origin-request") {
768
+ if (assoc.EventType === "origin-request" || assoc.EventType === "origin-response") {
768
769
  assoc.LambdaFunctionARN = newVersionArn;
770
+ assoc.EventType = "origin-request";
769
771
  }
770
772
  }
771
773
  }
@@ -963,7 +965,7 @@ function aws(awsConfig = {}) {
963
965
  const cf = new import_client_cloudfront.CloudFrontClient({ region: res.region });
964
966
  await updateCloudFrontEdgeVersion(cf, res.distributionId, newVersionArn);
965
967
  console.log("[qlara/aws] Bundling renderer...");
966
- const rendererZip = await bundleRenderer(config.routeFile, cacheTtl);
968
+ const rendererZip = await bundleRenderer(config.routeFile, cacheTtl, config.framework);
967
969
  await (0, import_client_lambda.waitUntilFunctionUpdatedV2)(
968
970
  { client: lambda, maxWaitTime: 120 },
969
971
  { FunctionName: res.rendererFunctionArn }
package/dist/aws.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as ProviderResources, Q as QlaraProvider } from './types-gl2xFqEX.cjs';
1
+ import { P as ProviderResources, Q as QlaraProvider } from './types--KPPgCtc.cjs';
2
2
 
3
3
  interface AwsConfig {
4
4
  stackName?: string;
package/dist/aws.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as ProviderResources, Q as QlaraProvider } from './types-gl2xFqEX.js';
1
+ import { P as ProviderResources, Q as QlaraProvider } from './types--KPPgCtc.js';
2
2
 
3
3
  interface AwsConfig {
4
4
  stackName?: string;
package/dist/aws.js CHANGED
@@ -558,7 +558,7 @@ async function bundleEdgeHandler(config) {
558
558
  });
559
559
  return createZip(outfile, "edge-handler.js");
560
560
  }
561
- async function bundleRenderer(routeFile, cacheTtl = 3600) {
561
+ async function bundleRenderer(routeFile, cacheTtl = 3600, framework) {
562
562
  mkdirSync(BUNDLE_DIR, { recursive: true });
563
563
  const outfile = join2(BUNDLE_DIR, "renderer.js");
564
564
  const alias = {};
@@ -580,7 +580,8 @@ async function bundleRenderer(routeFile, cacheTtl = 3600) {
580
580
  minify: true,
581
581
  alias,
582
582
  define: {
583
- __QLARA_CACHE_TTL__: String(cacheTtl)
583
+ __QLARA_CACHE_TTL__: String(cacheTtl),
584
+ __QLARA_FRAMEWORK__: JSON.stringify(framework || "")
584
585
  },
585
586
  external: []
586
587
  });
@@ -760,8 +761,9 @@ async function updateCloudFrontEdgeVersion(cf, distributionId, newVersionArn) {
760
761
  const lambdaAssociations = config.DefaultCacheBehavior?.LambdaFunctionAssociations;
761
762
  if (lambdaAssociations?.Items) {
762
763
  for (const assoc of lambdaAssociations.Items) {
763
- if (assoc.EventType === "origin-request") {
764
+ if (assoc.EventType === "origin-request" || assoc.EventType === "origin-response") {
764
765
  assoc.LambdaFunctionARN = newVersionArn;
766
+ assoc.EventType = "origin-request";
765
767
  }
766
768
  }
767
769
  }
@@ -959,7 +961,7 @@ function aws(awsConfig = {}) {
959
961
  const cf = new CloudFrontClient({ region: res.region });
960
962
  await updateCloudFrontEdgeVersion(cf, res.distributionId, newVersionArn);
961
963
  console.log("[qlara/aws] Bundling renderer...");
962
- const rendererZip = await bundleRenderer(config.routeFile, cacheTtl);
964
+ const rendererZip = await bundleRenderer(config.routeFile, cacheTtl, config.framework);
963
965
  await waitUntilFunctionUpdatedV2(
964
966
  { client: lambda, maxWaitTime: 120 },
965
967
  { FunctionName: res.rendererFunctionArn }
package/dist/cli.js CHANGED
@@ -569,7 +569,7 @@ async function bundleEdgeHandler(config) {
569
569
  });
570
570
  return createZip(outfile, "edge-handler.js");
571
571
  }
572
- async function bundleRenderer(routeFile, cacheTtl = 3600) {
572
+ async function bundleRenderer(routeFile, cacheTtl = 3600, framework) {
573
573
  mkdirSync(BUNDLE_DIR, { recursive: true });
574
574
  const outfile = join2(BUNDLE_DIR, "renderer.js");
575
575
  const alias = {};
@@ -591,7 +591,8 @@ async function bundleRenderer(routeFile, cacheTtl = 3600) {
591
591
  minify: true,
592
592
  alias,
593
593
  define: {
594
- __QLARA_CACHE_TTL__: String(cacheTtl)
594
+ __QLARA_CACHE_TTL__: String(cacheTtl),
595
+ __QLARA_FRAMEWORK__: JSON.stringify(framework || "")
595
596
  },
596
597
  external: []
597
598
  });
@@ -768,8 +769,9 @@ async function updateCloudFrontEdgeVersion(cf, distributionId, newVersionArn) {
768
769
  const lambdaAssociations = config.DefaultCacheBehavior?.LambdaFunctionAssociations;
769
770
  if (lambdaAssociations?.Items) {
770
771
  for (const assoc of lambdaAssociations.Items) {
771
- if (assoc.EventType === "origin-request") {
772
+ if (assoc.EventType === "origin-request" || assoc.EventType === "origin-response") {
772
773
  assoc.LambdaFunctionARN = newVersionArn;
774
+ assoc.EventType = "origin-request";
773
775
  }
774
776
  }
775
777
  }
@@ -967,7 +969,7 @@ function aws(awsConfig = {}) {
967
969
  const cf = new CloudFrontClient({ region: res.region });
968
970
  await updateCloudFrontEdgeVersion(cf, res.distributionId, newVersionArn);
969
971
  console.log("[qlara/aws] Bundling renderer...");
970
- const rendererZip = await bundleRenderer(config.routeFile, cacheTtl);
972
+ const rendererZip = await bundleRenderer(config.routeFile, cacheTtl, config.framework);
971
973
  await waitUntilFunctionUpdatedV2(
972
974
  { client: lambda, maxWaitTime: 120 },
973
975
  { FunctionName: res.rendererFunctionArn }
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as QlaraPluginConfig, b as QlaraRoute, c as QlaraManifest, M as ManifestRoute, R as RouteMatch } from './types-gl2xFqEX.cjs';
2
- export { P as ProviderResources, d as QlaraAlternateLinkDescriptor, e as QlaraAlternateURLs, f as QlaraAppLinks, g as QlaraAppLinksAndroid, h as QlaraAppLinksApple, i as QlaraAppLinksWeb, j as QlaraAppLinksWindows, k as QlaraAppleImage, l as QlaraAppleImageDescriptor, m as QlaraAppleWebApp, n as QlaraAuthor, o as QlaraDeployConfig, p as QlaraFacebook, q as QlaraFormatDetection, r as QlaraIcon, s as QlaraIconDescriptor, t as QlaraIcons, u as QlaraItunesApp, v as QlaraMetaDataGenerator, w as QlaraMetadata, x as QlaraOGAudio, y as QlaraOGAudioDescriptor, z as QlaraOGImage, A as QlaraOGImageDescriptor, B as QlaraOGVideo, C as QlaraOGVideoDescriptor, D as QlaraOpenGraph, E as QlaraOpenGraphArticle, F as QlaraOpenGraphBase, G as QlaraOpenGraphBook, H as QlaraOpenGraphMusicAlbum, I as QlaraOpenGraphMusicPlaylist, J as QlaraOpenGraphMusicRadioStation, K as QlaraOpenGraphMusicSong, L as QlaraOpenGraphProfile, N as QlaraOpenGraphVideoEpisode, O as QlaraOpenGraphVideoMovie, S as QlaraOpenGraphVideoOther, T as QlaraOpenGraphVideoTVShow, U as QlaraOpenGraphWebsite, V as QlaraPinterest, Q as QlaraProvider, W as QlaraReferrer, X as QlaraRobots, Y as QlaraRobotsInfo, Z as QlaraRouteDefinition, _ as QlaraRoutes, $ as QlaraTwitter, a0 as QlaraTwitterApp, a1 as QlaraTwitterAppDescriptor, a2 as QlaraTwitterBase, a3 as QlaraTwitterImage, a4 as QlaraTwitterImageDescriptor, a5 as QlaraTwitterPlayer, a6 as QlaraTwitterPlayerDescriptor, a7 as QlaraTwitterSummary, a8 as QlaraTwitterSummaryLargeImage, a9 as QlaraVerification } from './types-gl2xFqEX.cjs';
1
+ import { a as QlaraPluginConfig, b as QlaraRoute, c as QlaraManifest, M as ManifestRoute, R as RouteMatch } from './types--KPPgCtc.cjs';
2
+ export { P as ProviderResources, d as QlaraAlternateLinkDescriptor, e as QlaraAlternateURLs, f as QlaraAppLinks, g as QlaraAppLinksAndroid, h as QlaraAppLinksApple, i as QlaraAppLinksWeb, j as QlaraAppLinksWindows, k as QlaraAppleImage, l as QlaraAppleImageDescriptor, m as QlaraAppleWebApp, n as QlaraAuthor, o as QlaraDeployConfig, p as QlaraFacebook, q as QlaraFormatDetection, r as QlaraIcon, s as QlaraIconDescriptor, t as QlaraIcons, u as QlaraItunesApp, v as QlaraMetaDataGenerator, w as QlaraMetadata, x as QlaraOGAudio, y as QlaraOGAudioDescriptor, z as QlaraOGImage, A as QlaraOGImageDescriptor, B as QlaraOGVideo, C as QlaraOGVideoDescriptor, D as QlaraOpenGraph, E as QlaraOpenGraphArticle, F as QlaraOpenGraphBase, G as QlaraOpenGraphBook, H as QlaraOpenGraphMusicAlbum, I as QlaraOpenGraphMusicPlaylist, J as QlaraOpenGraphMusicRadioStation, K as QlaraOpenGraphMusicSong, L as QlaraOpenGraphProfile, N as QlaraOpenGraphVideoEpisode, O as QlaraOpenGraphVideoMovie, S as QlaraOpenGraphVideoOther, T as QlaraOpenGraphVideoTVShow, U as QlaraOpenGraphWebsite, V as QlaraPinterest, Q as QlaraProvider, W as QlaraReferrer, X as QlaraRobots, Y as QlaraRobotsInfo, Z as QlaraRouteDefinition, _ as QlaraRoutes, $ as QlaraTwitter, a0 as QlaraTwitterApp, a1 as QlaraTwitterAppDescriptor, a2 as QlaraTwitterBase, a3 as QlaraTwitterImage, a4 as QlaraTwitterImageDescriptor, a5 as QlaraTwitterPlayer, a6 as QlaraTwitterPlayerDescriptor, a7 as QlaraTwitterSummary, a8 as QlaraTwitterSummaryLargeImage, a9 as QlaraVerification } from './types--KPPgCtc.cjs';
3
3
 
4
4
  declare function validateConfig(config: QlaraPluginConfig, routes: QlaraRoute[]): void;
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as QlaraPluginConfig, b as QlaraRoute, c as QlaraManifest, M as ManifestRoute, R as RouteMatch } from './types-gl2xFqEX.js';
2
- export { P as ProviderResources, d as QlaraAlternateLinkDescriptor, e as QlaraAlternateURLs, f as QlaraAppLinks, g as QlaraAppLinksAndroid, h as QlaraAppLinksApple, i as QlaraAppLinksWeb, j as QlaraAppLinksWindows, k as QlaraAppleImage, l as QlaraAppleImageDescriptor, m as QlaraAppleWebApp, n as QlaraAuthor, o as QlaraDeployConfig, p as QlaraFacebook, q as QlaraFormatDetection, r as QlaraIcon, s as QlaraIconDescriptor, t as QlaraIcons, u as QlaraItunesApp, v as QlaraMetaDataGenerator, w as QlaraMetadata, x as QlaraOGAudio, y as QlaraOGAudioDescriptor, z as QlaraOGImage, A as QlaraOGImageDescriptor, B as QlaraOGVideo, C as QlaraOGVideoDescriptor, D as QlaraOpenGraph, E as QlaraOpenGraphArticle, F as QlaraOpenGraphBase, G as QlaraOpenGraphBook, H as QlaraOpenGraphMusicAlbum, I as QlaraOpenGraphMusicPlaylist, J as QlaraOpenGraphMusicRadioStation, K as QlaraOpenGraphMusicSong, L as QlaraOpenGraphProfile, N as QlaraOpenGraphVideoEpisode, O as QlaraOpenGraphVideoMovie, S as QlaraOpenGraphVideoOther, T as QlaraOpenGraphVideoTVShow, U as QlaraOpenGraphWebsite, V as QlaraPinterest, Q as QlaraProvider, W as QlaraReferrer, X as QlaraRobots, Y as QlaraRobotsInfo, Z as QlaraRouteDefinition, _ as QlaraRoutes, $ as QlaraTwitter, a0 as QlaraTwitterApp, a1 as QlaraTwitterAppDescriptor, a2 as QlaraTwitterBase, a3 as QlaraTwitterImage, a4 as QlaraTwitterImageDescriptor, a5 as QlaraTwitterPlayer, a6 as QlaraTwitterPlayerDescriptor, a7 as QlaraTwitterSummary, a8 as QlaraTwitterSummaryLargeImage, a9 as QlaraVerification } from './types-gl2xFqEX.js';
1
+ import { a as QlaraPluginConfig, b as QlaraRoute, c as QlaraManifest, M as ManifestRoute, R as RouteMatch } from './types--KPPgCtc.js';
2
+ export { P as ProviderResources, d as QlaraAlternateLinkDescriptor, e as QlaraAlternateURLs, f as QlaraAppLinks, g as QlaraAppLinksAndroid, h as QlaraAppLinksApple, i as QlaraAppLinksWeb, j as QlaraAppLinksWindows, k as QlaraAppleImage, l as QlaraAppleImageDescriptor, m as QlaraAppleWebApp, n as QlaraAuthor, o as QlaraDeployConfig, p as QlaraFacebook, q as QlaraFormatDetection, r as QlaraIcon, s as QlaraIconDescriptor, t as QlaraIcons, u as QlaraItunesApp, v as QlaraMetaDataGenerator, w as QlaraMetadata, x as QlaraOGAudio, y as QlaraOGAudioDescriptor, z as QlaraOGImage, A as QlaraOGImageDescriptor, B as QlaraOGVideo, C as QlaraOGVideoDescriptor, D as QlaraOpenGraph, E as QlaraOpenGraphArticle, F as QlaraOpenGraphBase, G as QlaraOpenGraphBook, H as QlaraOpenGraphMusicAlbum, I as QlaraOpenGraphMusicPlaylist, J as QlaraOpenGraphMusicRadioStation, K as QlaraOpenGraphMusicSong, L as QlaraOpenGraphProfile, N as QlaraOpenGraphVideoEpisode, O as QlaraOpenGraphVideoMovie, S as QlaraOpenGraphVideoOther, T as QlaraOpenGraphVideoTVShow, U as QlaraOpenGraphWebsite, V as QlaraPinterest, Q as QlaraProvider, W as QlaraReferrer, X as QlaraRobots, Y as QlaraRobotsInfo, Z as QlaraRouteDefinition, _ as QlaraRoutes, $ as QlaraTwitter, a0 as QlaraTwitterApp, a1 as QlaraTwitterAppDescriptor, a2 as QlaraTwitterBase, a3 as QlaraTwitterImage, a4 as QlaraTwitterImageDescriptor, a5 as QlaraTwitterPlayer, a6 as QlaraTwitterPlayerDescriptor, a7 as QlaraTwitterSummary, a8 as QlaraTwitterSummaryLargeImage, a9 as QlaraVerification } from './types--KPPgCtc.js';
3
3
 
4
4
  declare function validateConfig(config: QlaraPluginConfig, routes: QlaraRoute[]): void;
5
5
 
@@ -153,7 +153,8 @@ function withQlara(qlaraConfig) {
153
153
  },
154
154
  outputDir,
155
155
  routeFile: (0, import_node_path.resolve)(qlaraConfig.routeFile),
156
- env
156
+ env,
157
+ framework: "next"
157
158
  };
158
159
  (0, import_node_fs.mkdirSync)(QLARA_DIR, { recursive: true });
159
160
  (0, import_node_fs.writeFileSync)(
@@ -1,5 +1,5 @@
1
1
  import { NextConfig } from 'next';
2
- import { a as QlaraPluginConfig } from '../types-gl2xFqEX.cjs';
2
+ import { a as QlaraPluginConfig } from '../types--KPPgCtc.cjs';
3
3
 
4
4
  /**
5
5
  * Wrap a Next.js config with Qlara.
@@ -1,5 +1,5 @@
1
1
  import { NextConfig } from 'next';
2
- import { a as QlaraPluginConfig } from '../types-gl2xFqEX.js';
2
+ import { a as QlaraPluginConfig } from '../types--KPPgCtc.js';
3
3
 
4
4
  /**
5
5
  * Wrap a Next.js config with Qlara.
@@ -84,7 +84,8 @@ function withQlara(qlaraConfig) {
84
84
  },
85
85
  outputDir,
86
86
  routeFile: resolve(qlaraConfig.routeFile),
87
- env
87
+ env,
88
+ framework: "next"
88
89
  };
89
90
  mkdirSync(QLARA_DIR, { recursive: true });
90
91
  writeFileSync(
@@ -368,6 +368,12 @@ interface QlaraPluginConfig {
368
368
  provider: QlaraProvider;
369
369
  /** Env var names to forward to the renderer Lambda (values read from process.env at build time) */
370
370
  env?: string[];
371
+ /**
372
+ * The framework identifier. Set automatically by framework plugins (e.g. 'next' by withQlara).
373
+ * Used by the renderer to enable framework-specific post-render behavior
374
+ * (e.g. generating .txt RSC flight data files for Next.js client-side navigation).
375
+ */
376
+ framework?: string;
371
377
  }
372
378
  interface QlaraProvider {
373
379
  name: string;
@@ -394,6 +400,11 @@ interface QlaraDeployConfig {
394
400
  routeFile: string;
395
401
  /** Environment variables for the renderer Lambda (key-value pairs resolved at build time) */
396
402
  env?: Record<string, string>;
403
+ /**
404
+ * The framework identifier (e.g. 'next'). Set by framework plugins.
405
+ * Passed to the renderer to enable framework-specific post-render behavior.
406
+ */
407
+ framework?: string;
397
408
  }
398
409
  interface QlaraManifest {
399
410
  version: 1;
@@ -368,6 +368,12 @@ interface QlaraPluginConfig {
368
368
  provider: QlaraProvider;
369
369
  /** Env var names to forward to the renderer Lambda (values read from process.env at build time) */
370
370
  env?: string[];
371
+ /**
372
+ * The framework identifier. Set automatically by framework plugins (e.g. 'next' by withQlara).
373
+ * Used by the renderer to enable framework-specific post-render behavior
374
+ * (e.g. generating .txt RSC flight data files for Next.js client-side navigation).
375
+ */
376
+ framework?: string;
371
377
  }
372
378
  interface QlaraProvider {
373
379
  name: string;
@@ -394,6 +400,11 @@ interface QlaraDeployConfig {
394
400
  routeFile: string;
395
401
  /** Environment variables for the renderer Lambda (key-value pairs resolved at build time) */
396
402
  env?: Record<string, string>;
403
+ /**
404
+ * The framework identifier (e.g. 'next'). Set by framework plugins.
405
+ * Passed to the renderer to enable framework-specific post-render behavior.
406
+ */
407
+ framework?: string;
397
408
  }
398
409
  interface QlaraManifest {
399
410
  version: 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qlara",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Runtime ISR for static React apps — dynamic routing and SEO metadata for statically exported Next.js apps on AWS",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -54,6 +54,7 @@ import type {
54
54
  // At bundle time: '__qlara_routes__' → './qlara.routes.ts' (or wherever the dev put it)
55
55
  // Injected at bundle time by esbuild define
56
56
  declare const __QLARA_CACHE_TTL__: number;
57
+ declare const __QLARA_FRAMEWORK__: string;
57
58
 
58
59
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
59
60
  // @ts-ignore — resolved at bundle time by esbuild alias
@@ -759,6 +760,34 @@ function metadataToRscEntries(metadata: QlaraMetadata): string {
759
760
  return entries.join('');
760
761
  }
761
762
 
763
+ /**
764
+ * Extract RSC flight data from rendered HTML (Next.js-specific).
765
+ *
766
+ * Next.js embeds RSC data inside <script>self.__next_f.push([1,"..."])</script> blocks.
767
+ * The .txt file is these payloads concatenated with JSON string escapes resolved.
768
+ * Next.js's client-side router fetches the .txt file for client-side navigation
769
+ * instead of the full .html — so we must generate it for renderer-created pages.
770
+ *
771
+ * Only called when __QLARA_FRAMEWORK__ === 'next'.
772
+ */
773
+ function extractRscFlightData(html: string): string | null {
774
+ const chunks: string[] = [];
775
+ const regex = /self\.__next_f\.push\(\[1,"((?:[^"\\]|\\.)*)"\]\)/g;
776
+ let match;
777
+
778
+ while ((match = regex.exec(html)) !== null) {
779
+ // Unescape the JSON string: \" → ", \\ → \, \n → newline
780
+ const unescaped = match[1]
781
+ .replace(/\\n/g, '\n')
782
+ .replace(/\\"/g, '"')
783
+ .replace(/\\\\/g, '\\');
784
+ chunks.push(unescaped);
785
+ }
786
+
787
+ if (chunks.length === 0) return null;
788
+ return chunks.join('');
789
+ }
790
+
762
791
  export async function handler(event: RendererEvent & { warmup?: boolean }): Promise<RendererResult> {
763
792
  // Warmup invocation — just initialize the runtime and return
764
793
  if (event.warmup) {
@@ -825,7 +854,7 @@ export async function handler(event: RendererEvent & { warmup?: boolean }): Prom
825
854
  }
826
855
  }
827
856
 
828
- // 5. Upload to S3
857
+ // 5. Upload HTML to S3
829
858
  await s3.send(
830
859
  new PutObjectCommand({
831
860
  Bucket: bucket,
@@ -836,6 +865,26 @@ export async function handler(event: RendererEvent & { warmup?: boolean }): Prom
836
865
  })
837
866
  );
838
867
 
868
+ // 6. Framework-specific post-render uploads
869
+ // Next.js: extract RSC flight data and upload as .txt for client-side navigation.
870
+ // Next.js fetches .txt instead of .html when using <Link> / client-side nav.
871
+ // Without this, client-side nav falls back to a full page reload (slow).
872
+ if (__QLARA_FRAMEWORK__ === 'next') {
873
+ const rscData = extractRscFlightData(html);
874
+ if (rscData) {
875
+ const txtKey = s3Key.replace(/\.html$/, '.txt');
876
+ await s3.send(
877
+ new PutObjectCommand({
878
+ Bucket: bucket,
879
+ Key: txtKey,
880
+ Body: rscData,
881
+ ContentType: 'text/plain; charset=utf-8',
882
+ CacheControl: `public, max-age=0, s-maxage=${__QLARA_CACHE_TTL__}, stale-while-revalidate=60`,
883
+ })
884
+ );
885
+ }
886
+ }
887
+
839
888
  return {
840
889
  statusCode: 200,
841
890
  body: JSON.stringify({
package/src/types.ts CHANGED
@@ -513,6 +513,12 @@ export interface QlaraPluginConfig {
513
513
  provider: QlaraProvider;
514
514
  /** Env var names to forward to the renderer Lambda (values read from process.env at build time) */
515
515
  env?: string[];
516
+ /**
517
+ * The framework identifier. Set automatically by framework plugins (e.g. 'next' by withQlara).
518
+ * Used by the renderer to enable framework-specific post-render behavior
519
+ * (e.g. generating .txt RSC flight data files for Next.js client-side navigation).
520
+ */
521
+ framework?: string;
516
522
  }
517
523
 
518
524
  export interface QlaraProvider {
@@ -542,6 +548,11 @@ export interface QlaraDeployConfig {
542
548
  routeFile: string;
543
549
  /** Environment variables for the renderer Lambda (key-value pairs resolved at build time) */
544
550
  env?: Record<string, string>;
551
+ /**
552
+ * The framework identifier (e.g. 'next'). Set by framework plugins.
553
+ * Passed to the renderer to enable framework-specific post-render behavior.
554
+ */
555
+ framework?: string;
545
556
  }
546
557
 
547
558
  export interface QlaraManifest {