LLM Reference
LLM Reference: Content Rendering
This is a dense reference for LLMs working with Vextro’s frontend content rendering APIs. Import from vextro/content. Astro components are available as direct file imports.
Import Path
import {
renderTipTapToHtml,
extractPlainText,
buildSrcSet,
buildImageSizes,
focalPointToObjectPosition,
groupVariantsByFormat,
hasModernFormat,
getLargestVariant,
getSmallestVariant,
} from 'vextro/content';
Types
type TipTapDoc = { type: 'doc'; content: TipTapNode[] };
type TipTapNode = {
type: string;
attrs?: Record<string, unknown>;
content?: TipTapNode[];
marks?: TipTapMark[];
text?: string;
};
type TipTapMark = { type: string; attrs?: Record<string, unknown> };
// Accepted by renderTipTapToHtml and extractPlainText
type RichTextContent = TipTapDoc | string | null | undefined;
// Minimal file record shape accepted by image utilities and VextroImage
type VextroImageSource = {
url: string;
focalX?: number;
focalY?: number;
sizes?: ImageVariant[];
};
type ImageVariant = {
name: string;
url: string;
width: number;
height: number;
mimeType: string;
filesize: number;
};
// Block types
type BlockReference = { blockType: string; blockId: string; order?: number };
type ResolvedBlock = BlockReference & { data: Record<string, unknown> };
type BlockRenderer = (props: { block: ResolvedBlock; [key: string]: unknown }) => unknown;
type ContentField = { type: 'richText' | 'blocks' | 'html'; value: unknown };
Rich Text Functions
renderTipTapToHtml(content: RichTextContent): string
Converts TipTap JSON to semantic HTML. Handles all standard node types: paragraph, heading (h1–h6), bulletList, orderedList, listItem, blockquote, table, tableRow, tableCell, tableHeader, codeBlock, hardBreak, horizontalRule, and custom vextroBlock. Returns "" for null/undefined.
const html = renderTipTapToHtml(post.body);
extractPlainText(content: RichTextContent): string
Strips all markup; block elements are followed by \n. Suitable for excerpts and search index entries.
const excerpt = extractPlainText(post.body).slice(0, 160);
Image Utility Functions
buildSrcSet(variants: ImageVariant[] | undefined, options?: { format?: string }): string
Returns an srcset attribute string from an array of variants. Optionally filter to a single MIME type string (e.g. "webp", "avif").
const srcset = buildSrcSet(file.sizes, { format: 'webp' });
// "image-800.webp 800w, image-1200.webp 1200w"
buildImageSizes(breakpoints: Record<string, string>): string
Returns a sizes attribute string. The default key provides the fallback value (appended last, without a media query).
const sizes = buildImageSizes({
'(min-width: 1024px)': '50vw',
default: '100vw',
});
focalPointToObjectPosition(focalX?: number, focalY?: number): string
Converts 0–1 focal point coordinates to a CSS object-position percentage string.
focalPointToObjectPosition(0.3, 0.7) // "30% 70%"
groupVariantsByFormat(variants: ImageVariant[] | undefined): Map<string, ImageVariant[]>
Groups variants by mimeType. Returns an empty Map if variants is undefined.
hasModernFormat(variants: ImageVariant[] | undefined): boolean
Returns true if any variant has mimeType containing "webp" or "avif".
getLargestVariant(source: VextroImageSource): string
Returns the URL of the variant with the greatest width. Falls back to source.url.
getSmallestVariant(source: VextroImageSource): string
Returns the URL of the variant with the smallest width. Falls back to source.url.
Astro Components
VextroImage.astro
import VextroImage from 'vextro/content/VextroImage.astro';
Renders <picture> with AVIF and WebP <source> elements and a fallback <img>. Applies object-position from focalX/focalY.
Props:
source: VextroImageSource— file recordalt: string— requiredsizes?: string— HTML sizes attributewidth?: number,height?: number— explicit dimensionsloading?: "lazy" | "eager"fetchpriority?: "high" | "low" | "auto"class?: string— on<picture>imgClass?: string— on<img>focalX?: number,focalY?: number— override focal point (0–1)
VextroRichText.astro
import VextroRichText from 'vextro/content/VextroRichText.astro';
Wraps renderTipTapToHtml output in a semantic element. Renders nothing if content is empty.
Props:
content: RichTextContent— TipTap JSON or HTML stringclass?: stringas?: "div" | "article" | "section" | "main" | "aside"— default"div"
VextroBlocks.astro
import VextroBlocks from 'vextro/content/VextroBlocks.astro';
Maps ResolvedBlock[] to renderer components by blockType. Unknown types show a dev-only placeholder (hidden in production).
Props:
blocks: ResolvedBlock[]— resolved blocks withdatapopulatedcomponents: Record<string, Component>— blockType → Astro component (plain object)class?: stringas?: string— wrapper element tag, default"div"
VextroContent.astro
import VextroContent from 'vextro/content/VextroContent.astro';
Unified renderer for mixed content sequences (richText → blocks → richText patterns).
Props:
sections: ContentField[]— array of{ type, value }sectionsblockComponents?: Record<string, Component>— blockType → Astro component (plain object)class?: stringrichTextClass?: stringblocksClass?: stringas?: string
Block Resolution Pattern
Blocks are stored as references (blockType + blockId). Before passing to VextroBlocks, resolve each reference by loading its data from Convex:
// In your Astro page (server-side)
const rawBlocks: BlockReference[] = post.blocks ?? [];
const resolvedBlocks: ResolvedBlock[] = await Promise.all(
rawBlocks.map(async (ref) => ({
...ref,
data: await ctx.runQuery(api.blocks.getBlock, { blockType: ref.blockType, blockId: ref.blockId }),
}))
);