perf: edge-cache static assets and shrink org-route bundle

The /org/[slug] routes loaded 250+ JS chunks and, after a deploy, every
chunk missed Cloudflare's edge cache and stampeded the single origin (up
to 48s to serve one static file) — the "keeps loading" lockout.

- tenant-router: opt /_app/immutable/* into the edge cache via
  cf.cacheEverything; setting Cache-Control alone never populated it
- course/api: mark the 14 API singletons /* @__PURE__ */ so the barrel
  tree-shakes instead of dragging every API into each importer
- courses page: import directly instead of via the feature barrels

Cuts the courses route from 257 to 225 static chunks and keeps static
assets off the origin after the first edge fill.
This commit is contained in:
Rotimi Best
2026-06-12 01:51:05 +01:00
parent 8fd80e9dc5
commit c868b2f23f
16 changed files with 29 additions and 22 deletions
@@ -63,4 +63,4 @@ export class AttendanceApi extends BaseApiWithErrors {
}
}
export const attendanceApi = new AttendanceApi();
export const attendanceApi = /* @__PURE__ */ new AttendanceApi();
@@ -215,4 +215,4 @@ export class ComplianceApi extends BaseApiWithErrors {
}
}
export const complianceApi = new ComplianceApi();
export const complianceApi = /* @__PURE__ */ new ComplianceApi();
@@ -59,4 +59,4 @@ export class ContentApi extends BaseApiWithErrors {
}
}
export const contentApi = new ContentApi();
export const contentApi = /* @__PURE__ */ new ContentApi();
@@ -77,4 +77,4 @@ export class CourseCloneApi extends BaseApiWithErrors {
}
}
export const courseCloneApi = new CourseCloneApi();
export const courseCloneApi = /* @__PURE__ */ new CourseCloneApi();
@@ -754,4 +754,4 @@ export class CourseApi extends BaseApiWithErrors {
}
}
export const courseApi = new CourseApi();
export const courseApi = /* @__PURE__ */ new CourseApi();
@@ -171,4 +171,4 @@ export class CoursesApi extends BaseApiWithErrors {
}
}
export const coursesApi = new CoursesApi();
export const coursesApi = /* @__PURE__ */ new CoursesApi();
@@ -70,4 +70,4 @@ class ExerciseTemplateApi extends BaseApi {
}
}
export const exerciseTemplateApi = new ExerciseTemplateApi();
export const exerciseTemplateApi = /* @__PURE__ */ new ExerciseTemplateApi();
@@ -328,4 +328,4 @@ export class ExerciseApi extends BaseApiWithErrors {
*/
}
export const exerciseApi = new ExerciseApi();
export const exerciseApi = /* @__PURE__ */ new ExerciseApi();
@@ -945,4 +945,4 @@ export class LessonApi extends BaseApiWithErrors {
}
}
export const lessonApi = new LessonApi();
export const lessonApi = /* @__PURE__ */ new LessonApi();
@@ -35,4 +35,4 @@ export class MarkApi extends BaseApiWithErrors {
}
}
export const markApi = new MarkApi();
export const markApi = /* @__PURE__ */ new MarkApi();
@@ -507,4 +507,4 @@ export class NewsfeedApi extends BaseApiWithErrors {
}
}
export const newsfeedApi = new NewsfeedApi();
export const newsfeedApi = /* @__PURE__ */ new NewsfeedApi();
@@ -292,4 +292,4 @@ export class PeopleApi extends BaseApiWithErrors {
}
}
export const peopleApi = new PeopleApi();
export const peopleApi = /* @__PURE__ */ new PeopleApi();
@@ -76,4 +76,4 @@ export class PresignApi extends BaseApiWithErrors {
}
}
export const presignApi = new PresignApi();
export const presignApi = /* @__PURE__ */ new PresignApi();
@@ -159,4 +159,4 @@ export class SubmissionApi extends BaseApiWithErrors {
}
}
export const submissionApi = new SubmissionApi();
export const submissionApi = /* @__PURE__ */ new SubmissionApi();
@@ -1,6 +1,7 @@
<script lang="ts">
import { CoursesPage } from '$features/course/pages';
import { CreateCourseButton, CourseFilterPopover } from '$features/course/components';
import CoursesPage from '$features/course/pages/courses.svelte';
import CreateCourseButton from '$features/course/components/create-course-button.svelte';
import CourseFilterPopover from '$features/course/components/course-filter-popover.svelte';
import { courseMetaDeta } from '$features/course/utils/store';
import {
CourseSortBy,
@@ -15,7 +16,7 @@
import { t } from '$lib/utils/functions/translations';
import { onMount } from 'svelte';
import * as Page from '@cio/ui/base/page';
import { coursesApi } from '$features/course/api';
import { coursesApi } from '$features/course/api/courses.svelte';
let { data } = $props();
const getInitialSelectedTags = () => data.activeTags ?? [];
+11 -5
View File
@@ -265,17 +265,23 @@ export default {
upstreamHeaders.set('x-forwarded-for', clientIp);
}
// SvelteKit's immutable chunk filenames include a content hash, so they are
// safe to cache at the edge forever. `cf.cacheEverything` is what actually
// populates Cloudflare's edge cache for this subrequest — setting a
// `Cache-Control` header on the response alone does NOT, so without this a
// cold browser cache (new device, or post-deploy hash change) stampedes the
// origin for every chunk.
const isImmutableAsset = url.pathname.startsWith('/_app/immutable/');
const upstreamResponse = await fetch(upstreamUrl.toString(), {
method: request.method,
headers: upstreamHeaders,
body: request.body,
redirect: 'manual'
redirect: 'manual',
cf: isImmutableAsset ? { cacheEverything: true, cacheTtl: 31536000 } : undefined
});
// SvelteKit's immutable chunk filenames include a content hash, so they
// are safe to cache forever. Set a long TTL here at the edge so browsers
// and Cloudflare cache them aggressively rather than revalidating.
if (url.pathname.startsWith('/_app/immutable/')) {
if (isImmutableAsset) {
const response = new Response(upstreamResponse.body, upstreamResponse);
response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
return response;