Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/base/realm-config.gts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
containsMany,
linksTo,
} from './card-api';
import BooleanField from './boolean';
import StringField from './string';
import FileSettingsIcon from '@cardstack/boxel-icons/file-settings';
import LinkIcon from '@cardstack/boxel-icons/link';
Expand All @@ -31,6 +32,17 @@ export class RealmConfig extends CardDef {
@field backgroundURL = contains(StringField);
@field iconURL = contains(StringField);
@field hostRoutingRules = containsMany(RoutingRuleField);
// Opt-in to keeping the full prerendered isolated HTML for the
// realm's default CardsGrid index card. Default behaviour for this
// card writes a small boilerplate placeholder instead — the
// CardsGrid isolated render fans out into a fitted render per card
// in the realm and dominates indexing wall-clock on larger realms,
// and nothing reads its isolated HTML in production for an
// unpublished realm. Set this to `true` when the realm's index is
// served as published-realm SSR (the publish handler writes it
// automatically in that case) or when an operator otherwise needs
// the full isolated render present in the index.
@field includePrerenderedDefaultRealmIndex = contains(BooleanField);

@field cardTitle = contains(StringField, {
computeVia: function (this: RealmConfig) {
Expand Down
16 changes: 16 additions & 0 deletions packages/host/app/components/card-prerender.gts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
} from '../routes/module';

import { createAuthErrorGuard } from '../utils/auth-error-guard';
import { REALM_INDEX_BOILERPLATE_HTML } from '../utils/realm-index-boilerplate';
import {
RenderCardTypeTracker,
type CardRenderContext,
Expand All @@ -57,6 +58,8 @@ import {
withTimersBlocked,
} from '../utils/render-timer-stub';

import type { Model as HtmlRouteModel } from '../routes/render/html';

import type LoaderService from '../services/loader-service';
import type LocalIndexer from '../services/local-indexer';
import type NetworkService from '../services/network';
Expand Down Expand Up @@ -600,6 +603,19 @@ export default class CardPrerender extends Component {
if (this.localIndexer.renderError) {
throw new Error(this.localIndexer.renderError);
}
// The html sub-route flags this when the card is the realm's
// default CardsGrid index and the realm has not opted in to
// keeping its prerendered isolated HTML. Short-circuit the
// Glimmer render entirely and return the boilerplate so the
// indexer pays a constant write cost regardless of realm size.
if (
format === 'isolated' &&
ancestorLevel === 0 &&
(routeInfo.attributes as HtmlRouteModel).useRealmIndexBoilerplate
) {
Comment thread
habdelra marked this conversation as resolved.
await this.#ensureRenderReady(routeInfo);
return REALM_INDEX_BOILERPLATE_HTML;
}
let component = routeInfo.attributes.Component;
this.#renderErrorPayload = undefined;
let captureMode: 'innerHTML' | 'outerHTML' | 'textContent';
Expand Down
67 changes: 64 additions & 3 deletions packages/host/app/routes/render/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ import type RouterService from '@ember/routing/router-service';
import type Transition from '@ember/routing/transition';
import { service } from '@ember/service';

import { isValidFormat } from '@cardstack/runtime-common';
import {
internalKeyFor,
isRealmIndexCardId,
isValidFormat,
realmURL,
type CodeRef,
type ResolvedCodeRef,
} from '@cardstack/runtime-common';

import type RealmService from '@cardstack/host/services/realm';

import type {
BoxComponent,
Expand All @@ -15,14 +24,34 @@ import { getClass, getTypes } from './meta';

import type { Model as ParentModel } from '../render';

// Stable internal key for the base CardsGrid type. We compare against
// the internalKeyFor representation of the cards-grid module + name so
// the check tolerates whatever resolved form the host's identify path
// produces (e.g. realm-aliased base URLs).
const CARDS_GRID_REF = {
module: 'https://cardstack.com/base/cards-grid',
name: 'CardsGrid',
} as ResolvedCodeRef;
const CARDS_GRID_INTERNAL_KEY = internalKeyFor(CARDS_GRID_REF, undefined);

export interface Model {
instance: CardDef;
format: Format;
Component: BoxComponent;
// True when this render should be short-circuited to the realm-
// index boilerplate placeholder instead of running through Glimmer.
// Set only when `format === 'isolated'`, the card is the realm's
// default index, the type chain is base CardsGrid, and the realm
// has not opted in via `includePrerenderedDefaultRealmIndex` on its
// RealmConfig card. The orchestrator in `card-prerender.gts`
// honours this flag by substituting the boilerplate string and
// skipping the actual Glimmer render.
useRealmIndexBoilerplate?: boolean;
}

export default class RenderHtmlRoute extends Route<Model> {
@service declare router: RouterService;
@service declare realm: RealmService;

beforeModel(transition: Transition) {
let parentModel = this.modelFor('render') as ParentModel | undefined;
Expand Down Expand Up @@ -69,14 +98,46 @@ export default class RenderHtmlRoute extends Route<Model> {
if (isNaN(level)) {
throw new Error('not a valid ancestor_level');
}
let componentCodeRef = getTypes(getClass(instance))[level];
let types = getTypes(getClass(instance));
let componentCodeRef = types[level];
if (!componentCodeRef) {
throw new Error(`ancestor_level ${level} does not exist`);
}

let Component = instance.constructor.getComponent(instance, undefined, {
componentCodeRef,
});
return { format, instance, Component };

let useRealmIndexBoilerplate =
format === 'isolated' &&
level === 0 &&
this.#isDefaultRealmCardsGridIndex(instance, types);

return { format, instance, Component, useRealmIndexBoilerplate };
}

// True when the card under render is the realm's default index card
// AND its type chain begins with the base CardsGrid AND the realm
// has NOT opted in to keeping its prerendered isolated HTML via
// `RealmInfo.includePrerenderedDefaultRealmIndex`. The orchestrator
// substitutes a boilerplate placeholder for the captured HTML in
// that case so the indexer doesn't pay for the (expensive) grid
// fan-out render of every card in the realm. Published realms
// receive the opt-in automatically from the publish handler.
#isDefaultRealmCardsGridIndex(instance: CardDef, types: CodeRef[]): boolean {
let cardRealmURL = instance[realmURL];
if (!cardRealmURL || !isRealmIndexCardId(instance.id, cardRealmURL)) {
return false;
}
let topType = types[0];
if (!topType) {
return false;
}
let topKey = internalKeyFor(topType, undefined);
if (topKey !== CARDS_GRID_INTERNAL_KEY) {
return false;
}
let info = this.realm.info(cardRealmURL.href);
return info?.includePrerenderedDefaultRealmIndex !== true;
Comment thread
habdelra marked this conversation as resolved.
}
}
27 changes: 27 additions & 0 deletions packages/host/app/utils/realm-index-boilerplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Placeholder content the host returns as `isolatedHTML` when it
// decides to skip the full isolated render for the realm's default
// CardsGrid index card. The decision is made per-render in
// `card-prerender.gts` based on the card URL, its adoptsFrom chain,
// and the realm's `includePrerenderedDefaultRealmIndex` config field
// — see the realm-index opt-in PR for the surrounding rationale.
//
// The boilerplate is intentionally minimal. Anywhere the placeholder
// could be visible (the published-realm SSR injection slot, the
// error-page `lastKnownGoodHtml` fallback for an erroring realm
// index, or any future consumer reading `isolated_html` directly) is
// out of the search-result hot path; we just need valid HTML that
// the Ember runtime can replace cleanly on hydration. The wrapper
// shape matches what `withTimeout` in
// `packages/realm-server/prerender/utils.ts` captures from a real
// render so downstream consumers don't see a structurally different
// payload.
export const REALM_INDEX_BOILERPLATE_HTML = `<section data-prerender>
<div
class="boxel-cards-grid-shell"
data-boxel-cards-grid-index
aria-busy="true"
>
Prerendered HTML for default realm index is disabled (can be configured in realm.json)
</div>
</section>
`;
Loading
Loading