---
title: "Quickstart"
description: "Add an authenticated AI assistant to your Next.js app. Run the handler in your deployment with mordn-hosted persistence and configuration."
---

Add an AI assistant inside your authenticated Next.js App Router application.
This guide uses **your runtime with mordn-hosted infrastructure**: your server
verifies the session and runs the model; mordn hosts conversations, attachments,
knowledge, memory, and published configuration.

You will add a server route and a client component, configure server credentials,
and render the assistant inside your signed-in product.

> **Note**
>
> **Prerequisites:** Next.js 14/15/16 with App Router and a server deployment,
> React 18 or 19, and an existing authentication setup — Clerk, Auth.js,
> Supabase Auth, or your own verified sessions. The example below assumes Clerk
> is already installed and configured. Tailwind is not required; the widget
> stylesheet is precompiled.
>
> You also need a published mordn agent, its server-only `MORDN_CHAT_KEY`, and
> AI Gateway credentials for model calls. Configure `AI_GATEWAY_API_KEY` for
> local development or deployments without managed Vercel AI Gateway credentials.
> Selecting a model in the dashboard does not supply those credentials.
>
> **Want conversations in your own Postgres?** Follow
> [Bring your own database](/docs/quickstart/self-host) instead. Same widget,
> same handler, same security model — you supply the store.

## Choose where the handler runs

| | Your runtime (this guide) | [Direct hosted mode](/docs/hosted-mode) |
| --- | --- | --- |
| Handler and model execution | Your server deployment | mordn |
| Browser connection | Your `/api/chat` route | mordn hosted endpoint |
| Agent credential | Server-only `mck_live_…` | Browser-safe `pk_live_…` |
| Signed-in identity | `getUserId` reads a verified server session | `getUserToken` supplies a signed JWT, verified by mordn |
| Model credentials in your app | Your AI Gateway credentials (or a provider key with a code-defined model) | Not required |

Both use hosted infrastructure. Direct hosted mode is a separate setup, not
what `createMordnHandler` enables in this guide. It requires hosted mode enabled
on the agent, allowed application origins, and identity-provider JWT/JWKS
settings for signed-in users. A publishable key selects the agent; it does not
authenticate a user. See [Hosted mode](/docs/hosted-mode) for that setup.

## Optional: scaffold the hosted integration

With widget **0.24.0 or later**, run this from an existing Next.js App Router project:

```bash
npx @mordn/chat-widget@^0.24.0 init --hosted
```

This is the **your-runtime** mode described here, not direct hosted execution.
It generates the catch-all route, a server auth stub, a client component and a
separate `.env.mordn.example`. It does not install dependencies, change your layout,
overwrite files or secrets, provision a database, or run migrations.

The auth stub deliberately returns `null`: implement your verified server session,
configure the published agent key and gateway/model credentials, and mount the
component before expecting chat to work. Existing chat routes are refused so the
wizard cannot mix a new backend with an old identity boundary. If you already have
an integration, use the manual steps below instead of deleting it to rerun setup.

1. **Create an agent and copy its key**

    [Create an account](https://mordn.com/sign-up), add an agent, and publish it. Publishing is what
    gives the agent a model, a system prompt, a theme, and its enabled features.

    Copy the agent's server key. It looks like `mck_live_…` and is read only on your
    server — never ship it to the browser.

    ```env title=".env.local"
    MORDN_CHAT_KEY="mck_live_..."
    # Local development or deployments without managed AI Gateway credentials:
    AI_GATEWAY_API_KEY="your_ai_gateway_key"
    ```

    Set the required secrets in your deployment environment too. Neither key
    belongs in a `NEXT_PUBLIC_` variable or client component.

2. **Install**

    ```bash
    npm install @mordn/chat-widget ai @ai-sdk/react
    ```

    These are the chat dependencies to add to your existing authenticated app.
    This path needs no database driver, storage SDK, or model-provider package.
    The published agent config supplies a model identifier, not model credentials.

3. **Add the route**

    ```ts title="app/api/chat/[[...chat]]/route.ts"
    import { createMordnHandler } from '@mordn/chat-widget/server';
    import { auth } from '@clerk/nextjs/server';

    export const { GET, POST, DELETE, OPTIONS } = createMordnHandler({
      apiKey: process.env.MORDN_CHAT_KEY!,
      getUserId: async () => (await auth()).userId,
    });
    ```

    `apiKey` and `getUserId` are the only required options. That one key wires
    persistence, private attachments, published configuration, knowledge retrieval,
    memory, hosted MCP tools, and feedback. Feature behavior depends on your
    published configuration; the key does not replace your model credentials.

    Swap the Clerk import for whatever you already use. The contract is the same:
    return a stable user id from a **verified server session**, or `null`.

    ```ts title="Auth.js"
    import { auth } from '@/auth';

    getUserId: async () => (await auth())?.user?.id ?? null,
    ```

    > **Warning**
    >
    > Never derive this id from a header, query parameter, or request body. Those are
    > controlled by the browser. `getUserId` is the entire authorization boundary —
    > the store and storage are bound to whatever it returns. Return `null` when
    > unauthenticated and the handler responds `401` before touching user data.

4. **Mount the widget**

    ```tsx title="components/assistant.tsx"
    'use client';

    import { ChatWidget } from '@mordn/chat-widget';
    import '@mordn/chat-widget/styles.css';

    export function Assistant() {
      return <ChatWidget />;
    }
    ```

    That is the whole component. `apiBase` defaults to `/api/chat`; pass it
    explicitly if you mounted the route somewhere else.

    On mount the widget calls `GET ${apiBase}/bootstrap` and the server returns only
    what the browser is allowed to see — greeting, theme, layout, features, starter
    prompts. The widget never sends a `userId`, so it cannot assert who it is or
    whose history it may read.

5. **Send a message**

    Render `<Assistant />` inside a signed-in page or layout, sign in, and type. The first message
    creates the conversation; reload the page and it is still there.

## Infrastructure you do not need to run

Both columns below use **your runtime**; this compares hosted infrastructure
with bringing your own database, not direct hosted mode.

| | mordn-hosted infrastructure | Bring your own database |
| --- | --- | --- |
| Provision Postgres | Not needed | Required |
| Run migrations | Not needed | Your reviewed migration process |
| Create a private bucket | Not needed | Required for uploads |
| Model credentials | Your AI Gateway credentials | Credentials for your chosen model/provider |

> **Note**
>
> **How the model runs.** When you do not pass `model` in code, the handler uses
> the model from your published agent config and hands that string to the AI SDK,
> which routes it through the AI Gateway. Use managed Vercel AI Gateway
> credentials where available; otherwise, set `AI_GATEWAY_API_KEY`.
>
> In **this setup**, inference runs from your deployment using your gateway
> credentials; mordn provides persistence and configuration. This is different
> from direct hosted mode, where mordn runs the handler and model calls.
>
> To pin a model in code instead, install the provider package and pass `model` —
> see [Models & tools](/docs/guides/models-and-tools). Code always wins over
> published configuration.

> **Verify before shipping**
>
> - Two signed-in users cannot load, continue, delete, or attach files to each
>   other's conversations.
> - Signed-out requests get `401`, not an empty conversation.
> - History survives a refresh.
> - `MORDN_CHAT_KEY` and any `AI_GATEWAY_API_KEY` appear only in server-side
>   configuration, never in a `NEXT_PUBLIC_` variable or browser bundle.
> - A model response streams successfully in both local and deployed environments;
>   publishing an agent alone does not verify your model credentials.
> - If the widget and handler are on different origins, configure `cors` and
>   `requestCredentials` deliberately.

## Next steps

**[Bring your own database](/docs/quickstart/self-host)**

Keep conversation data in your own Postgres.

**[Security model](/docs/security)**

Why browser identity is never authorization.

**[Build actions](/docs/guides/build-actions)**

Let the agent do things in your product.

**[Theming](/docs/guides/theming)**

Make it look like yours.
