Documentation Index
Fetch the complete documentation index at: https://docs.gradial.com/llms.txt
Use this file to discover all available pages before exploring further.
Framework Guides
Agentic Content Infrastructure (ACI) works with your existing frontend project. This page covers how to set up each supported framework, from installing the runtime to building your first component.
The aci-learn repository contains a complete Astro starter you can clone and run locally.
Astro
Astro is ACI’s recommended starting point. Its island architecture and static-first approach align naturally with ACI’s pre-compile model.
Setup
# Create a new Astro project or use an existing one
npm create astro@latest my-site
cd my-site
# Add the ACI runtime
npm install @aci/runtime zod
Config File
Create .aci.yaml in your project root:
# .aci.yaml
version: "1"
siteId: "your_site_id"
framework: astro
source:
root: "./"
componentRegistry: ./src/cms/components.ts
layoutRegistry: ./src/cms/layouts.ts
rendererEntry: ./src/cms/renderer.ts
capabilities:
staticRender: true
ssr: true
ssrIslands: true
clientIslands: true
routes:
cmsManaged: "/[...slug]"
frameworkOwned:
- "/api/*"
- "/_astro/*"
rendererProtocol: stdio-json
Component Registry
Define your components with Zod schemas in src/cms/components.ts:
// src/cms/components.ts
import { defineComponent } from '@aci/runtime';
import { z } from 'zod';
const imageSchema = z.object({
src: z.string().min(1),
alt: z.string().optional(),
});
export default [
defineComponent({
name: 'hero',
schema: z.object({
headline: z.string().min(1),
subtext: z.string().optional(),
image: imageSchema,
cta: z.object({
label: z.string().min(1),
href: z.string().min(1),
}).optional(),
}),
renderModes: { canStatic: true, canSSR: true, canClientIsland: false },
}),
defineComponent({
name: 'product_card',
schema: z.object({
name: z.string().min(1),
price: z.string().min(1),
description: z.string().optional(),
image: imageSchema,
}),
renderModes: { canStatic: true, canSSR: true, canClientIsland: true },
defaultIslandMode: 'client',
}),
];
Layout Registry
Define your page layouts in src/cms/layouts.ts:
// src/cms/layouts.ts
import { defineLayout } from '@aci/runtime';
import { z } from 'zod';
export default [
defineLayout({
name: 'marketing',
schema: z.object({}),
regions: ['main'],
}),
defineLayout({
name: 'product',
schema: z.object({
showBreadcrumbs: z.boolean().optional(),
}),
regions: ['main', 'sidebar'],
}),
];
Renderer Entry
Create the renderer that ACI calls to produce HTML in src/cms/renderer.ts:
// src/cms/renderer.ts
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import RenderPage from '../render/RenderPage.astro';
import type { GradialRenderer } from '@aci/runtime';
const renderer: GradialRenderer = {
async renderPage(request) {
const container = await AstroContainer.create();
const html = await container.renderToString(RenderPage, {
props: {
input: {
route: request.requestContext?.url || '/',
page: request.page,
siteConfig: request.siteConfig,
}
}
});
return {
html,
status: 200,
cachePolicy: { scope: 'public', ttl: 60 }
};
}
};
export default renderer;
Component Implementation
Create your Astro component that matches the registry schema:
---
// src/components/sections/Hero.astro
interface Props {
headline: string;
subtext?: string;
image: { src: string; alt?: string };
cta?: { label: string; href: string };
}
const { headline, subtext, image, cta } = Astro.props;
---
<section class="hero py-20 bg-gradient-to-b from-indigo-50 to-white">
<div class="max-w-6xl mx-auto px-4 grid md:grid-cols-2 gap-12 items-center">
<div>
<h1 class="text-5xl font-bold text-gray-900">{headline}</h1>
{subtext && <p class="mt-4 text-xl text-gray-600">{subtext}</p>}
{cta && (
<a href={cta.href} class="mt-8 inline-block px-6 py-3 bg-indigo-600 text-white rounded-lg">
{cta.label}
</a>
)}
</div>
<img src={image.src} alt={image.alt || ''} class="rounded-xl shadow-lg" />
</div>
</section>
Block Registry (Component Mapping)
Map component names to their implementations in src/components/blockRegistry.ts:
// src/components/blockRegistry.ts
import Hero from './sections/Hero.astro';
import ProductCard from './blocks/ProductCard.astro';
export const blockRegistry: Record<string, any> = {
hero: Hero,
product_card: ProductCard,
};
Next.js
ACI supports Next.js with both the App Router and Pages Router. The same capsule interface works for SSG and SSR pages.
Setup
npm install @aci/runtime zod
Config
# .aci.yaml
version: "1"
siteId: "your_site_id"
framework: nextjs
source:
root: "./"
componentRegistry: ./src/cms/components.ts
layoutRegistry: ./src/cms/layouts.ts
rendererEntry: ./src/cms/renderer.ts
capabilities:
staticRender: true
ssr: true
Component Example
// src/components/Hero.tsx
interface HeroProps {
headline: string;
subtext?: string;
image: { src: string; alt?: string };
cta?: { label: string; href: string };
}
export function Hero({ headline, subtext, image, cta }: HeroProps) {
return (
<section className="hero py-20 bg-gradient-to-b from-indigo-50 to-white">
<div className="max-w-6xl mx-auto px-4 grid md:grid-cols-2 gap-12 items-center">
<div>
<h1 className="text-5xl font-bold text-gray-900">{headline}</h1>
{subtext && <p className="mt-4 text-xl text-gray-600">{subtext}</p>}
{cta && (
<a href={cta.href} className="mt-8 inline-block px-6 py-3 bg-indigo-600 text-white rounded-lg">
{cta.label}
</a>
)}
</div>
<img src={image.src} alt={image.alt || ''} className="rounded-xl shadow-lg" />
</div>
</section>
);
}
SvelteKit
ACI supports SvelteKit with static prerendering and server-side rendering.
Setup
npm install @aci/runtime zod
Config
# .aci.yaml
version: "1"
siteId: "your_site_id"
framework: sveltekit
source:
root: "./"
componentRegistry: ./src/lib/cms/components.ts
layoutRegistry: ./src/lib/cms/layouts.ts
rendererEntry: ./src/lib/cms/renderer.ts
capabilities:
staticRender: true
ssr: true
Component Example
<!-- src/lib/components/Hero.svelte -->
<script lang="ts">
export let headline: string;
export let subtext: string | undefined = undefined;
export let image: { src: string; alt?: string };
export let cta: { label: string; href: string } | undefined = undefined;
</script>
<section class="hero py-20 bg-gradient-to-b from-indigo-50 to-white">
<div class="max-w-6xl mx-auto px-4 grid md:grid-cols-2 gap-12 items-center">
<div>
<h1 class="text-5xl font-bold text-gray-900">{headline}</h1>
{#if subtext}
<p class="mt-4 text-xl text-gray-600">{subtext}</p>
{/if}
{#if cta}
<a href={cta.href} class="mt-8 inline-block px-6 py-3 bg-indigo-600 text-white rounded-lg">
{cta.label}
</a>
{/if}
</div>
<img src={image.src} alt={image.alt || ''} class="rounded-xl shadow-lg" />
</div>
</section>
Local Development
All frameworks work with ACI’s local development loop:
# Start the local ACI server (runs in background)
aci serve
# In another terminal, start your framework's dev server
npm run dev
# Or use the combined command
aci dev
This gives you:
- Hot-reloading preview at localhost:4321 (Astro) or your framework’s default port
- Content files in
.content/ that you can edit directly
- Instant feedback — edit JSON content, refresh the page, see changes
Content Structure
Content lives in .content/ as JSON files:
.content/
├── config/
│ └── site.json # Site-wide settings (nav, footer, etc.)
└── pages/
├── home/
│ └── _index.json # Homepage content
└── product/
└── widget/
└── _index.json # /product/widget page content
Example Page Content
{
"id": "home",
"$type": "page",
"status": "published",
"layout": "marketing",
"metadata": {
"title": "Welcome to My Site",
"description": "The best site ever built."
},
"regions": {
"main": [
{
"id": "hero-1",
"component": "hero",
"props": {
"headline": "Build faster with ACI",
"subtext": "Content infrastructure that gets out of your way.",
"image": {
"src": "https://example.com/hero.jpg",
"alt": "Hero image"
},
"cta": {
"label": "Get started",
"href": "/docs"
}
}
}
]
}
}
Edit the JSON, refresh your browser, and see the changes immediately.
Key Concepts
defineComponent
Every component in your registry is defined with defineComponent():
defineComponent({
name: 'hero', // Matches "component" field in content JSON
schema: z.object({...}), // Zod schema for validation
renderModes: {
canStatic: true, // Can render at build time
canSSR: true, // Can render on server per-request
canClientIsland: false, // Can hydrate on client
},
defaultIslandMode: 'static', // Default if not specified in content
})
defineLayout
Layouts define the structure of pages:
defineLayout({
name: 'marketing', // Matches "layout" field in content JSON
schema: z.object({...}), // Layout-level props (optional)
regions: ['main', 'sidebar'], // Named slots for components
})
GradialRenderer
Your renderer implements the GradialRenderer interface:
interface GradialRenderer {
renderPage(request: RenderPageRequest): Promise<RenderPageResponse>;
}
interface RenderPageRequest {
page: PageContent; // The validated page content
siteConfig?: SiteConfig; // Site-wide configuration
requestContext?: {
url: string; // The request URL
headers?: Record<string, string>;
};
}
interface RenderPageResponse {
html: string; // The rendered HTML
status: number; // HTTP status code
cachePolicy?: {
scope: 'public' | 'private';
ttl: number; // Seconds
};
}
Next: CLI Reference →