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

# From static report to live app

> Turn a static HTML report built with Claude or Codex into a live web app powered by Nekt.

When you connect Claude or Codex to Nekt through the [MCP Server](/mcp-server/introduction), it can explore your data and generate a polished HTML report in minutes. The problem is that report is frozen: the numbers are baked into the file, and the moment your data changes it is out of date.

This guide turns that static report into a **live app**, accessible over the web, that always reflects your current data.

<Info>
  **You do not need to write code.** Each step below gives you a prompt to paste into your AI agent. The agent creates the tables in Nekt, builds the app, and deploys it. The text around each prompt explains what is happening and how to confirm it worked.
</Info>

## How this works

The plan has three core steps, plus two optional enhancements:

<Steps>
  <Step title="Materialize the report data in Nekt">
    Turn the SQL behind your report into scheduled Queries that precompute clean, fast tables.
  </Step>

  <Step title="Turn the report into a live app">
    The agent adds a small server function that reads those tables from Nekt through the Data API, and wires your report to load from it.
  </Step>

  <Step title="Deploy with Vercel">
    Publish the app to a public URL, optionally connected to GitHub.
  </Step>
</Steps>

Two optional enhancements, [caching](#optional-add-caching) and [authentication](#optional-add-authentication), are covered along the way. Add them if your report needs them, or skip them for a first version.

### Architecture

The key idea is that your Nekt API key stays on the server. Your report never talks to Nekt directly. It calls a small server function that queries Nekt, caches the result, and returns clean data.

```mermaid theme={null}
flowchart LR
    A[Browser<br/>your report] -->|loads data| B[Server function]
    B -->|cached copy| A
    B -->|Data API| C[Nekt]
    C -->|materialized tables| D[(Catalog)]
```

## Before you start

<Steps>
  <Step title="Use an agent that can build and deploy">
    You need an agent that can create files and run commands in a project folder, such as **Claude Code** or the **Codex CLI**, connected to Nekt. Follow [Connect Claude](/integrations/claude) or [Connect Codex](/integrations/codex) if you have not already.

    <Note>Claude.ai on the web can create the Nekt Queries in Step 1, but it cannot build and deploy the app. For the full flow, use a coding agent.</Note>
  </Step>

  <Step title="Have your report and its SQL ready">
    Keep your static report file (the HTML the agent generated) and the SQL queries behind it. If the SQL is embedded in the HTML, that is fine, the agent can extract it.
  </Step>

  <Step title="Create a Nekt API key">
    In Nekt, go to **Workspace Settings > API Keys** and [create an API key](https://app.nekt.ai/settings/api-keys). You will paste it into the agent during deployment. Requires a **paid Nekt plan**.
  </Step>

  <Step title="Create a free Vercel account">
    Sign up at [vercel.com](https://vercel.com). This is where your live report will be hosted.
  </Step>
</Steps>

<Tip>
  In a hurry? Skip to the [all-in-one prompt](#fast-path-one-prompt) that runs the whole flow at once, then come back to any step you want to adjust.
</Tip>

***

## Step 1: Materialize the report data in Nekt

Your report was built from one or more SQL queries that join and aggregate raw tables. Running those joins every time someone opens the report is slow. Instead, you precompute them once into clean, ready-to-read tables that the app loads instantly.

A report is usually made of several sections (a KPI row, a revenue chart, a top-customers table, and so on), and each section is backed by its own query. You create **one materialized table per section**. A simple report may need just one table; a richer one may need several.

Paste this into your agent:

```text Prompt theme={null}
Using the Nekt MCP tools, take the SQL queries behind my report and turn each one
into a materialized table in Nekt.

For every query:
- Create a scheduled Query in Nekt that writes its results to a clean table in the
  trusted layer.
- Name each table report_<section>, for example report_monthly_metrics or
  report_top_customers.
- Schedule it to refresh daily.
- Shape each table so it matches exactly what that section of the report shows
  (one row per data point), so the app does not have to transform anything.

My queries are here: [paste your SQL, or say: they are embedded in report.html,
please extract them first].

When done, list the tables you created and their columns so I can confirm.
```

**Confirm it worked:** open your [Nekt Catalog](https://app.nekt.ai) and check that each new `report_*` table exists and holds the expected rows.

<Note>
  For logic SQL cannot express (machine learning, external API calls, complex reshaping), ask the agent to use a [Notebook](/notebooks/overview) instead. The result lands in the Catalog the same way, so the rest of this guide is unchanged.
</Note>

***

## Step 2: Turn the report into a live app

Now the agent builds a small app around your existing report. It keeps your layout and charts, and adds a server function that reads the materialized tables from Nekt's [Data API](/data-api/introduction) and hands the data to the page.

Paste this into your agent:

```text Prompt theme={null}
Turn my static report into a live web app that loads current data from Nekt.

- Keep my existing report layout, styling, and charts.
- Add a Vercel serverless function at api/report.js that reads my materialized
  Nekt tables through the Data API:
    - Endpoint: POST https://api.nekt.ai/api/v1/sql-query/
    - Header: x-api-key, read from the NEKT_API_KEY environment variable
    - Body: {"sql": "<a SELECT against one report_* table>", "mode": "csv"}
    - The response returns presigned_urls; download the CSV from the first URL
      and parse it into rows.
  Run one query per report table (report_monthly_metrics, report_top_customers,
  and so on), in parallel, and return all datasets as JSON keyed by section.
- Update the report to fetch /api/report when it loads and render each dataset
  into the matching section.
- Keep my Nekt API key server-side only. Never put it in the browser code.

Show me the project file tree and run it locally so I can preview it.
```

**Confirm it worked:** the agent runs the app locally (usually `http://localhost:3000`) and your report shows live data pulled from Nekt.

<Accordion title="Reference: the Data API call and the function the agent builds">
  The Data API runs a SQL query and returns a presigned URL to download the results:

  <CodeGroup>
    ```bash Request theme={null}
    curl --request POST \
      --url https://api.nekt.ai/api/v1/sql-query/ \
      --header 'x-api-key: YOUR_API_KEY' \
      --header 'Content-Type: application/json' \
      --data '{
        "sql": "SELECT month, revenue, new_customers FROM \"trusted\".\"report_monthly_metrics\" ORDER BY month",
        "mode": "csv"
      }'
    ```

    ```json Response theme={null}
    {
      "state": "SUCCEEDED",
      "presigned_urls": [
        "https://storage.googleapis.com/bucket/query-....csv?..."
      ],
      "data_scanned_in_bytes": 11250,
      "execution_time_in_millis": 408
    }
    ```
  </CodeGroup>

  <Warning>Presigned URLs expire after **1 hour**. The app caches the parsed data, never the URL.</Warning>

  The function lists every table in one place, runs the queries in parallel, and returns the datasets keyed by section:

  ```javascript api/report.js theme={null}
  // One entry per section of your report.
  const DATASETS = {
    metrics: `SELECT month, revenue, new_customers
              FROM "trusted"."report_monthly_metrics" ORDER BY month`,
    topCustomers: `SELECT name, total_spent
                   FROM "trusted"."report_top_customers" ORDER BY total_spent DESC`,
    kpis: `SELECT * FROM "trusted"."report_kpis"`,
  };

  export default async function handler(req, res) {
    const entries = await Promise.all(
      Object.entries(DATASETS).map(async ([key, sql]) => [key, await runQuery(sql)])
    );
    return res.status(200).json(Object.fromEntries(entries));
  }

  async function runQuery(sql) {
    const query = await fetch("https://api.nekt.ai/api/v1/sql-query/", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": process.env.NEKT_API_KEY,
      },
      body: JSON.stringify({ sql, mode: "csv" }),
    });

    const result = await query.json();
    if (result.state !== "SUCCEEDED" || !result.presigned_urls?.length) {
      throw new Error(`Query failed: ${result.state_change_reason ?? result.state}`);
    }

    const csv = await fetch(result.presigned_urls[0]).then((r) => r.text());
    return parseCsv(csv);
  }

  function parseCsv(text) {
    const [header, ...lines] = text.trim().split("\n");
    const cols = header.split(",");
    return lines.map((line) =>
      Object.fromEntries(cols.map((c, i) => [c, line.split(",")[i]]))
    );
  }
  ```
</Accordion>

***

## Optional: Add caching

Without caching, every visitor and every refresh triggers a new query to Nekt. Since your tables only change when the scheduled Queries rerun, you can safely serve a cached copy for a while. This makes the report load instantly and reduces load on Nekt.

Paste this into your agent:

```text Prompt theme={null}
Add caching to api/report.js so the app does not query Nekt on every page load.
Set the response header:
  Cache-Control: s-maxage=3600, stale-while-revalidate=86400
so Vercel's edge network serves a cached copy for one hour, then refreshes it in
the background. Cache the parsed data, not the presigned URLs (they expire after
an hour). If my tables refresh less often than hourly, use a longer cache time.
```

**Confirm it worked:** reload the report a few times. After the first load it should return instantly without querying Nekt again.

<Tip>
  For finer control or a cache shared across regions, ask the agent to use [Vercel KV](https://vercel.com/docs/storage/vercel-kv) with an explicit expiration instead of the header.
</Tip>

***

## Optional: Add authentication

If your report is meant to be public, skip this. If it holds internal data, gate it.

Paste one of these into your agent:

```text Prompt: simplest, no code theme={null}
My report is deployed on Vercel and I want to keep it private. Walk me through
turning on Password Protection (or Vercel Authentication) in the Vercel project
settings, so only people with the password or my Vercel team can open it.
```

```text Prompt: app-level password theme={null}
Add a simple password gate to my report using a REPORT_TOKEN environment variable.
Require the token in api/report.js, and add a small login prompt on the page that
stores it in the browser after the first entry. Do not expose the token in the
code.
```

**Confirm it worked:** open the report in a private browser window. You should be asked to authenticate before seeing any data.

<Warning>
  Whichever method you choose, your Nekt API key stays server-side only. Never let the agent place it in browser code.
</Warning>

***

## Step 3: Deploy with Vercel

Finally, the agent publishes the app to a public URL. Connecting a GitHub repository is recommended (every change you make later redeploys automatically) but optional.

Paste this into your agent:

```text Prompt theme={null}
Deploy this app to Vercel and give me the live URL.

- Set the environment variable NEKT_API_KEY to this value: [paste your Nekt API key].
  (Also set REPORT_TOKEN if we added a password.)
- Deploy to production.
- Recommended: initialize a git repository and push it to GitHub, then connect the
  repo to Vercel so future changes deploy automatically.
- If there is any step you cannot do for me (like authorizing GitHub or Vercel in
  the browser), stop and give me clear instructions.
```

**Confirm it worked:** open the URL the agent returns. Your report is now live and shows current data from Nekt.

<Warning>
  Set your Nekt API key as a Vercel **environment variable** only, as in the prompt above. Never commit it to your code or GitHub repository.
</Warning>

***

## Fast path: one prompt

If you would rather run the whole flow at once, paste this single prompt into your agent (connected to Nekt) after completing [Before you start](#before-you-start):

```text Prompt theme={null}
I have a static HTML report built on my Nekt data. Help me turn it into a live web
app deployed on Vercel. Do the following end to end, pausing only when you need
something from me (like my API key or a browser authorization):

1. Using the Nekt MCP tools, turn each SQL query behind my report into a scheduled
   Nekt Query that materializes a clean table in the trusted layer, named
   report_<section>, refreshing daily. My queries: [paste SQL, or point to the
   report file].

2. Build a Vercel app around my existing report. Add api/report.js that reads each
   materialized table via the Nekt Data API
   (POST https://api.nekt.ai/api/v1/sql-query/, header x-api-key from NEKT_API_KEY,
   body {"sql": ..., "mode": "csv"}, download the returned presigned CSV and parse
   it). Query all tables in parallel and return them as JSON keyed by section.
   Update the report to fetch /api/report on load. Keep the API key server-side.

3. Add edge caching with Cache-Control: s-maxage=3600, stale-while-revalidate=86400.

4. Ask me whether the report should be private. If yes, help me enable Vercel
   Password Protection or add a REPORT_TOKEN gate.

5. Deploy to Vercel with NEKT_API_KEY set as an environment variable, and give me
   the live URL. Offer to connect a GitHub repo for automatic redeploys.
```

***

## Keeping it fresh

Your report now updates itself: the scheduled Queries refresh the tables in Nekt, and the app reads the latest data on each cache cycle.

* **Change what it shows:** ask the agent to update the Nekt Query or the SQL in `api/report.js`.
* **Change how it looks:** ask the agent to edit the report and redeploy (automatic if you connected GitHub).

<CardGroup cols={2}>
  <Card title="Data API reference" icon="database" href="/data-api/introduction">
    Endpoint details, output formats, and limitations.
  </Card>

  <Card title="Queries overview" icon="code-simple" href="/queries/overview">
    Schedule and orchestrate the queries that feed your report.
  </Card>

  <Card title="Connect Claude" icon="message-bot" href="/integrations/claude">
    Build and edit the report with Claude over MCP.
  </Card>

  <Card title="Connect Codex" icon="code" href="/integrations/codex">
    Build and edit the report with Codex over MCP.
  </Card>
</CardGroup>

## Need help?

Contact our support team if you run into issues while building your live report.
