# @json-render/next

Next.js renderer. JSON becomes full Next.js applications with routes, layouts, metadata, and SSR.

## Installation

```bash
npm install @json-render/core @json-render/react @json-render/next
```

## schema

The Next.js app schema for multi-page specs. Use with `defineCatalog` from core.

```typescript

const catalog = defineCatalog(schema, {
  components: {
    Card: {
      props: z.object({ title: z.string() }),
      description: 'Card container',
    },
    NavBar: {
      props: z.object({ links: z.array(z.object({ href: z.string(), label: z.string() })) }),
      description: 'Navigation bar',
    },
  },
  actions: {},
});
```

## createNextApp

Create all exports needed for a Next.js `[[...slug]]` catch-all route.

```typescript

const { Page, generateMetadata, generateStaticParams } = createNextApp({
  spec: myAppSpec,
  loaders: {
    loadPost: async ({ slug }) => {
      const post = await db.post.findUnique({ where: { slug } });
      return { post };
    },
  },
});
```

### Options

<table>
  <thead>
    <tr>
      <th>Option</th>
      <th>Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>spec</code></td>
      <td><code>{'NextAppSpec | (() => NextAppSpec | Promise<NextAppSpec>)'}</code></td>
      <td>The application spec (static or dynamic)</td>
    </tr>
    <tr>
      <td><code>loaders</code></td>
      <td><code>{'Record<string, LoaderFn>'}</code></td>
      <td>Server-side data loaders keyed by name</td>
    </tr>
  </tbody>
</table>

### Returns

<table>
  <thead>
    <tr>
      <th>Export</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>Page</code></td>
      <td>Async Server Component for <code>page.tsx</code></td>
    </tr>
    <tr>
      <td><code>generateMetadata</code></td>
      <td>Metadata generator for Next.js SEO</td>
    </tr>
    <tr>
      <td><code>generateStaticParams</code></td>
      <td>Static params for pre-rendering at build time</td>
    </tr>
  </tbody>
</table>

## NextAppSpec

The top-level spec defining an entire Next.js application.

```typescript
interface NextAppSpec {
  metadata?: NextMetadata;
  routes: Record<string, NextRouteSpec>;
  layouts?: Record<string, Spec>;
  state?: Record<string, unknown>;
}
```

### Route Patterns

Routes use Next.js URL conventions:

<table>
  <thead>
    <tr>
      <th>Pattern</th>
      <th>Example Match</th>
      <th>Params</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>/</code></td>
      <td><code>/</code></td>
      <td><code>{'{}'}</code></td>
    </tr>
    <tr>
      <td><code>/about</code></td>
      <td><code>/about</code></td>
      <td><code>{'{}'}</code></td>
    </tr>
    <tr>
      <td><code>/blog/[slug]</code></td>
      <td><code>/blog/hello</code></td>
      <td><code>{'{ slug: "hello" }'}</code></td>
    </tr>
    <tr>
      <td><code>/docs/[...path]</code></td>
      <td><code>/docs/a/b/c</code></td>
      <td><code>{'{ path: ["a","b","c"] }'}</code></td>
    </tr>
    <tr>
      <td><code>/app/[[...path]]</code></td>
      <td><code>/app</code> or <code>/app/x/y</code></td>
      <td><code>{'{ path: [] }'}</code> or <code>{'{ path: ["x","y"] }'}</code></td>
    </tr>
  </tbody>
</table>

## NextAppProvider

Client component that provides the component registry and action handlers to all pages.

```tsx

  return (
    <NextAppProvider registry={registry} handlers={handlers}>
      {children}
    </NextAppProvider>
  );
}
```

## Built-in Components

### Slot

Placeholder in layouts where page content is rendered. Every layout MUST include a Slot.

```json
{ "type": "Slot", "props": {}, "children": [] }
```

### Link

Client-side navigation wrapping `next/link`.

```json
{ "type": "Link", "props": { "href": "/about" }, "children": ["link-text"] }
```

## Built-in Actions

<table>
  <thead>
    <tr>
      <th>Action</th>
      <th>Params</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>setState</code></td>
      <td><code>{'{ statePath, value }'}</code></td>
      <td>Update a value in state</td>
    </tr>
    <tr>
      <td><code>pushState</code></td>
      <td><code>{'{ statePath, value, clearStatePath? }'}</code></td>
      <td>Append to array in state</td>
    </tr>
    <tr>
      <td><code>removeState</code></td>
      <td><code>{'{ statePath, index }'}</code></td>
      <td>Remove from array by index</td>
    </tr>
    <tr>
      <td><code>navigate</code></td>
      <td><code>{'{ href }'}</code></td>
      <td>Client-side navigation</td>
    </tr>
  </tbody>
</table>

## Server Utilities

### matchRoute

Match a pathname against a spec's routes.

```typescript

const matched = matchRoute(spec, '/blog/hello-world');
// { route: NextRouteSpec, pattern: '/blog/[slug]', params: { slug: 'hello-world' } }
```

### resolveMetadata

Resolve merged metadata for a route.

```typescript

const metadata = resolveMetadata(spec, matchedRoute?.route);
```

### slugToPath

Convert catch-all slug array to pathname.

```typescript

slugToPath(undefined);          // "/"
slugToPath(['blog', 'hello']);  // "/blog/hello"
```

## Entry Points

<table>
  <thead>
    <tr>
      <th>Import</th>
      <th>Contents</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>@json-render/next</code></td>
      <td>Client components (NextAppProvider, PageRenderer, Link)</td>
    </tr>
    <tr>
      <td><code>@json-render/next/server</code></td>
      <td>Server utilities (createNextApp, matchRoute, schema)</td>
    </tr>
  </tbody>
</table>