> ## Documentation Index
> Fetch the complete documentation index at: https://modelrunner.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js App Router

> Create a Next.js app (App Router) with a simple form that calls a ModelRunner model using the JavaScript client.

## Overview

Build a minimal Next.js application using the App Router that accepts a prompt, routes requests through the ModelRunner server proxy, and displays the generated output.

<Callout>
  You will keep your ModelRunner key on the server and route all client requests through the proxy. The client never sees secrets.
</Callout>

## Prerequisites

* **Node.js 18+**
* **Next.js 13.4+ (App Router)**
* **ModelRunner account and API key**

<Tip>
  Find your key in your ModelRunner account. Store it as `MODELRUNNER_KEY` in your environment.
</Tip>

## Steps

<Steps>
  <Step title="Create a Next.js app">
    Use the official Next.js starter with the App Router.

    ```bash theme={null}
    npx create-next-app@latest nextjs-modelrunner --ts --eslint --app --src-dir --tailwind
    cd nextjs-modelrunner
    ```
  </Step>

  <Step title="Install the ModelRunner client and proxy">
    ```bash theme={null}
    npm i @modelrunner/client @modelrunner/server-proxy
    ```
  </Step>

  <Step title="Add environment variable">
    Create a `.env.local` file at the project root.

    ```bash theme={null}
    MODELRUNNER_KEY=your_modelrunner_key
    ```

    <Warning>
      Do not commit `.env.local`. Next.js ignores it by default via `.gitignore`.
    </Warning>
  </Step>

  <Step title="Add the proxy route (App Router)">
    Create a route under `app/api/modelrunner/proxy/route.ts` that re-exports the proxy handlers.

    ```ts theme={null}
    // app/api/modelrunner/proxy/route.ts
    import { route } from "@modelrunner/server-proxy/nextjs";

    export const { GET, POST, PUT } = route;
    ```
  </Step>

  <Step title="Configure the client to use the proxy">
    Set the proxy URL so client calls route through your server. Place this config in a client-side entry point (e.g., `app/page.tsx`) before making calls.

    ```ts theme={null}
    import { modelrunner } from "@modelrunner/client";

    modelrunner.config({
      proxyUrl: "/api/modelrunner/proxy",
    });
    ```
  </Step>

  <Step title="Build the UI: simple prompt form">
    Replace the home page with a form that calls the client (which uses the proxy) and renders results.

    ```tsx theme={null}
    // app/page.tsx
    'use client'
    import { useState } from "react";
    import { modelrunner } from "@modelrunner/client";

    modelrunner.config({
      proxyUrl: "/api/modelrunner/proxy",
    });

    export default function HomePage() {
      const [prompt, setPrompt] = useState("");
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState<string | null>(null);
      const [result, setResult] = useState<any>(null);

      async function onSubmit(e: React.FormEvent) {
        e.preventDefault();
        setLoading(true);
        setError(null);
        setResult(null);
        try {

          const response = await modelrunner.run("bytedance/sdxl-lightning-4step", {
            input: {
              prompt,
              negative_prompt: "worst quality, low quality, deformed, extra fingers",
              width: 1280,
              height: 1024,
              num_outputs: 1,
              scheduler: "K_EULER",
              num_inference_steps: 4,
              guidance_scale: 0,
              seed: 103,
              disable_safety_checker: "false",
            },
          });

          setResult(response.data);
        } catch (err) {
          setError(err instanceof Error ? err.message : "Unknown error");
        } finally {
          setLoading(false);
        }
      }

      return (
        <main className="mx-auto max-w-2xl p-6 space-y-6">
          <h1 className="text-2xl font-semibold">ModelRunner Image Generation</h1>
          <form onSubmit={onSubmit} className="space-y-4">
            <label className="block">
              <span className="text-sm font-medium">Prompt</span>
              <input
                className="mt-1 w-full rounded-md border p-2"
                placeholder="two friends cooking together"
                value={prompt}
                onChange={(e) => setPrompt(e.target.value)}
              />
            </label>
            <button
              type="submit"
              className="rounded-md bg-blue-500 px-4 py-2 text-white disabled:opacity-50"
              disabled={loading || !prompt}
            >
              {loading ? "Generating..." : "Generate"}
            </button>
          </form>

          {error && (
            <div className="rounded-md border border-red-300 bg-red-50 p-3 text-red-700">
              {error}
            </div>
          )}

          {result && (
            <section className="space-y-2">
              <h2 className="text-lg font-medium">Result</h2>
              <div className="text-sm text-gray-700">
                <div>
                  <span className="font-semibold">Status:</span> {result?.status}
                </div>
                {typeof result?.inferenceTime === "number" && (
                  <div>
                    <span className="font-semibold">Inference time:</span> {result.inferenceTime}s
                  </div>
                )}
              </div>

              {Array.isArray(result?.output) ? (
                <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
                  {result.output.map((item: any, index: number) => {
                    const url = typeof item === "string" ? item : item?.url || item?.image_url;
                    return url ? (
                      // eslint-disable-next-line @next/next/no-img-element
                      <img key={index} src={url} alt={`Generated ${index + 1}`} className="rounded-lg" />
                    ) : (
                      <pre key={index} className="overflow-auto rounded-md bg-gray-100 p-3 text-sm">
                        {JSON.stringify(item, null, 2)}
                      </pre>
                    );
                  })}
                </div>
              ) : result?.output ? (
                <pre className="overflow-auto rounded-md bg-gray-100 p-3 text-sm">
                  {JSON.stringify(result.output, null, 2)}
                </pre>
              ) : (
                <pre className="overflow-auto rounded-md bg-gray-100 p-3 text-sm">
                  {JSON.stringify(result, null, 2)}
                </pre>
              )}
            </section>
          )}
        </main>
      );
    }
    ```
  </Step>

  <Step title="Run the app">
    ```bash theme={null}
    npm run dev
    ```

    <Check>
      Visit `http://localhost:3000`. Enter a prompt and submit. You should see the API response or an image.
    </Check>
  </Step>
</Steps>

## Verification and troubleshooting

* **401 Unauthorized**: Confirm `MODELRUNNER_KEY` is set and your server restarted.
* **CORS issues**: Calls are server-to-server. Ensure your browser calls `/api/modelrunner`, not the external API directly.
* **Unexpected response shape**: Log `result` and adjust rendering. Some models return arrays or nested objects.

<AccordionGroup>
  <Accordion title="Using server actions instead of API route">
    You can call the client from a Server Action for form submissions. Ensure `modelrunner.config` and secret usage remain server-only.
  </Accordion>
</AccordionGroup>

## Reference

See the JavaScript client guide for more usage patterns.

<CardGroup cols={2}>
  <Card title="JavaScript Client" icon="square-code" href="/docs/clients/js-client">
    Install and use the ModelRunner JS/TS SDK.
  </Card>

  <Card title="API Reference" icon="terminal" href="/docs/api-reference/introduction">
    Explore endpoints and request/response formats.
  </Card>
</CardGroup>
