12k

@json-render/next

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

Installation#

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.

import { defineCatalog } from '@json-render/core';
import { schema } from '@json-render/next/server';
import { z } from 'zod';

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.

import { createNextApp } from '@json-render/next/server';

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

Options#

OptionTypeDescription
specNextAppSpec | (() => NextAppSpec | Promise<NextAppSpec>)The application spec (static or dynamic)
loadersRecord<string, LoaderFn>Server-side data loaders keyed by name

Returns#

ExportDescription
PageAsync Server Component for page.tsx
generateMetadataMetadata generator for Next.js SEO
generateStaticParamsStatic params for pre-rendering at build time

NextAppSpec#

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

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

Route Patterns#

Routes use Next.js URL conventions:

PatternExample MatchParams
//{}
/about/about{}
/blog/[slug]/blog/hello{ slug: "hello" }
/docs/[...path]/docs/a/b/c{ path: ["a","b","c"] }
/app/[[...path]]/app or /app/x/y{ path: [] } or { path: ["x","y"] }

NextAppProvider#

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

import { NextAppProvider } from '@json-render/next';

export default function Layout({ children }) {
  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.

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

Client-side navigation wrapping next/link.

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

Built-in Actions#

ActionParamsDescription
setState{ statePath, value }Update a value in state
pushState{ statePath, value, clearStatePath? }Append to array in state
removeState{ statePath, index }Remove from array by index
navigate{ href }Client-side navigation

Server Utilities#

matchRoute#

Match a pathname against a spec's routes.

import { matchRoute } from '@json-render/next/server';

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

resolveMetadata#

Resolve merged metadata for a route.

import { resolveMetadata } from '@json-render/next/server';

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

slugToPath#

Convert catch-all slug array to pathname.

import { slugToPath } from '@json-render/next/server';

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

Entry Points#

ImportContents
@json-render/nextClient components (NextAppProvider, PageRenderer, Link)
@json-render/next/serverServer utilities (createNextApp, matchRoute, schema)