> ## 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

> How to set up Astro, Next.js, and SvelteKit projects to work with Gradial ACI.

ACI works with your existing frontend project. Every supported framework connects through the same frontend contract, `.aci.yaml`, component and layout contracts, and a renderer entrypoint, so the concepts you learn for one carry over to the rest. The contract is shared; each framework simply decides how to render the validated page payload. This page walks through setting up each one, from installing the SDK to wiring your first component.

## Shared Setup

Install the SDK and TypeScript tooling:

```bash theme={null}
npm install @gradial/aci zod@^4
npm install -D typescript tsx
```

Use this package name in all framework integrations:

```ts theme={null}
import { defineComponentContract, defineLayoutContract, slot } from '@gradial/aci';
```

## Contract File Rules

Keep contract files separate from runtime components:

```txt theme={null}
src/cms/contracts/components/   Component names, schemas, render modes
src/cms/contracts/layouts/      Layout names and slot contracts
src/cms/renderer.ts             Renderer capsule entrypoint
src/components/                 Runtime framework components
```

Contract files may import `@gradial/aci` and `zod`. They should not import framework components, CSS, browser APIs, or runtime rendering code.

## Astro

Astro is ACI's recommended starting point. Its island architecture and static-first approach align naturally with ACI's pre-compile model, so it's the fastest way to get a feel for how content and code come together.

### Config

```yaml theme={null}
version: "1"
siteId: "your_site_id"
framework: astro

source:
  root: "./"

componentRegistry: ./src/cms/contracts/components/index.ts
layoutRegistry: ./src/cms/contracts/layouts/index.ts
rendererEntry: ./src/cms/renderer.ts

capabilities:
  staticRender: true
  ssr: true
  ssrIslands: true
  clientIslands: true
  fragmentRender: true

routes:
  cmsManaged: "/[...slug]"
  frameworkOwned:
    - "/api/*"
    - "/_astro/*"

rendererProtocol: stdio-json
```

### Component Contract

```ts theme={null}
// src/cms/contracts/components/homeHero.contract.ts
import { defineComponentContract } from '@gradial/aci';
import { z } from 'zod';

export const homeHeroContract = defineComponentContract({
  name: 'home_hero',
  schema: z.object({
    eyebrow: z.string().optional(),
    headline: z.string().min(1),
    description: z.string().optional(),
    ctaLabel: z.string().min(1),
    ctaHref: z.string().min(1)
  }),
  renderModes: { canStatic: true, canSSR: true, canClientIsland: false }
});
```

### Layout Contract

```ts theme={null}
// src/cms/contracts/layouts/index.ts
import { defineLayoutContract, slot } from '@gradial/aci';

export default [
  defineLayoutContract({
    name: 'marketing',
    slots: [slot('main', true), slot('footer')]
  })
];
```

### Renderer Entry

```ts theme={null}
// src/cms/renderer.ts
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import RenderPage from '../render/RenderPage.astro';
import type { GradialRenderer } from '@gradial/aci';

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
        }
      }
    });

    return {
      html,
      status: 200,
      cachePolicy: { scope: 'public', ttl: 60 }
    };
  }
};

export default renderer;
```

### Local Scripts

```json theme={null}
{
  "scripts": {
    "dev": "astro dev --host 0.0.0.0",
    "build": "ACI_CONTENT_ROOT=./.aci/compiled astro build",
    "preview": "ACI_CONTENT_ROOT=./.aci/compiled astro preview --host 0.0.0.0",
    "aci:compile": "aci build --compile-only",
    "content:compile": "aci build --skip-code --content ./.content --out ./.aci/compiled",
    "aci:build": "aci build --content ./.content",
    "aci:doctor": "aci doctor",
    "aci:validate": "npm run content:compile"
  }
}
```

## Next.js

Next.js plugs into the same contract, centered on the App Router and SSR path. If you already run Next.js, ACI slots in alongside it.

### Config

```yaml theme={null}
version: "1"
siteId: "your_site_id"
framework: next

source:
  root: "./"

componentRegistry: ./src/cms/contracts/components/index.ts
layoutRegistry: ./src/cms/contracts/layouts/index.ts
rendererEntry: ./src/cms/renderer.ts

capabilities:
  staticRender: true
  ssr: true
  ssrIslands: true
  clientIslands: true
  fragmentRender: true

routes:
  cmsManaged: "/[...slug]"
  frameworkOwned:
    - "/api/*"
    - "/_next/*"

rendererProtocol: stdio-json
```

### CMS Catch-All Route

Create a catch-all App Router page and force dynamic SSR:

```tsx theme={null}
// src/app/[[...slug]]/page.tsx
import { PageNotFoundError, routeFromNextParams } from '@gradial/aci/next/server';
import { loadRenderInputOrNotFound } from '@/lib/content';
import { RenderPage } from '@/render/RenderPage';

export const dynamic = 'force-dynamic';

export default async function Page({
  params
}: {
  params: Promise<{ slug?: string[] }>;
}) {
  const input = await loadRenderInputOrNotFound(await routeFromNextParams(params));
  if (!input) throw new PageNotFoundError('/');
  return <RenderPage input={input} />;
}
```

### Middleware

```ts theme={null}
// src/middleware.ts
import { createGradialMiddleware } from '@gradial/aci/next/middleware';

export default createGradialMiddleware({
  siteId: process.env.ACI_SITE_ID || '',
  edgeConfig: process.env.EDGE_CONFIG,
  previewSignKey: process.env.ACI_PREVIEW_SIGN_KEY
});

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|\\.gradial-dam|api/aci-assets|gradial/assets).*)']
};
```

### Release Asset Route

```ts theme={null}
// src/app/gradial/assets/[releaseId]/[...path]/route.ts
export { GET } from '@gradial/aci/next/asset-route';
```

Run `aci doctor` to verify the catch-all route, dynamic SSR settings, package dependency, and asset route.

## SvelteKit

SvelteKit uses the same `.aci.yaml` contract and a renderer function that returns HTML.

### Config

```yaml theme={null}
version: "1"
siteId: "your_site_id"
framework: sveltekit

source:
  root: "./"
  outDir: "build"
  publicDir: "static"

componentRegistry: ./src/cms/contracts/components/index.ts
layoutRegistry: ./src/cms/contracts/layouts/index.ts
rendererEntry: ./src/cms/renderer.ts

capabilities:
  staticRender: true
  ssr: true
  ssrIslands: true
  clientIslands: true
  fragmentRender: true

routes:
  cmsManaged: "/[...slug]"
  frameworkOwned:
    - "/api/*"
    - "/_app/*"

rendererProtocol: stdio-json
```

### Renderer Function

```ts theme={null}
// src/render/renderPage.ts
import { render } from 'svelte/server';
import RenderPage from './RenderPage.svelte';
import type { RenderInput, RenderOutput } from '@gradial/aci/content';

export async function renderPage(input: RenderInput): Promise<RenderOutput> {
  const result = render(RenderPage, { props: { input } });
  return {
    route: input.route || '/',
    html: [
      '<!doctype html>',
      '<html lang="en">',
      '<head>',
      '<meta charset="utf-8" />',
      result.head,
      '</head>',
      '<body>',
      result.body,
      '</body>',
      '</html>'
    ].join(''),
    assets: { css: [], js: [], images: [] },
    diagnostics: []
  };
}
```

## Local Content Structure

Content lives in `.content/` as JSON files:

```txt theme={null}
.content/
├── config/
│   └── site.json
└── pages/
    ├── home/
    │   └── _index.json
    └── product/
        └── aeroflow-pro/
            └── _index.json
```

Page documents use component names from the contract registry:

```json theme={null}
{
  "$type": "page",
  "id": "home",
  "status": "published",
  "layout": "marketing",
  "renderMode": "static",
  "metadata": {
    "title": "Welcome"
  },
  "regions": {
    "main": [
      {
        "id": "hero",
        "component": "home_hero",
        "props": {
          "headline": "Build faster with ACI"
        }
      }
    ]
  }
}
```

## Validation Loop

Run this before committing starter changes:

```bash theme={null}
aci doctor
aci build --compile-only
aci build --skip-code --content ./.content --out ./.aci/compiled
npm run typecheck
npm run build
```

***

*Next: [CLI Reference →](/aci/developers/cli-reference)*
