Write

# ASR Benchmark Schema & Agent Instructions

## Overview

This document defines the **canonical JSON schema** for all ASR benchmark outputs across all models. Every agent producing benchmark results MUST conform to this schema so the dashboard can ingest results uniformly.

---

## Models & Directory Layout

```
/home/ubuntu/training/benchmark_outputs/
  qwen3-asr/
    ckpt-24000/
      metrics.json
      sample_analysis.json
    ckpt-72000/
      metrics.json
      sample_analysis.json
    ckpt-100000/
      metrics.json
      sample_analysis.json
  gemma3n-e2b/
    ckpt-10000/
      metrics.json
      sample_analysis.json
    ckpt-20000/
      metrics.json
      sample_analysis.json
  parakeet-1.1b/
    baseline/
      metrics.json
      sample_analysis.json
```

### Model Registry

| Model ID              | Display Name                       | Has Checkpoints | Notes                     |
|-----------------------|------------------------------------|-----------------|---------------------------|
| `qwen3-asr`          | Qwen3-ASR-1.7B                    | Yes             | ckpt-24000, 72000, 100000 |
| `gemma3n-e2b`        | Gemma-3n-E2B-ASR                  | Yes             | ckpt-10000, 20000         |
| `parakeet-1.1b`      | Parakeet-1.1B-Language-Guided     | No (yet)        | Use `baseline` as folder  |

### Languages (12 Indic + English)

| Language   | Code | Source Dataset          | Samples |
|------------|------|-------------------------|---------|
| assamese   | as   | IndicVoices (valid)     | 500     |
| bengali    | bn   | Kathbath (test_known)   | 500     |
| english    | en   | Svarah                  | 500     |
| gujarati   | gu   | Kathbath (test_known)   | 500     |
| hindi      | hi   | Kathbath (test_known)   | 500     |
| kannada    | kn   | Kathbath (test_known)   | 500     |
| malayalam  | ml   | Kathbath (test_known)   | 500     |
| marathi    | mr   | Kathbath (test_known)   | 500     |
| odia       | or   | Kathbath (test_known)   | 500     |
| punjabi    | pa   | Kathbath (test_known)   | 500     |
| tamil      | ta   | Kathbath (test_known)   | 500     |
| telugu     | te   | Kathbath (test_known)   | 500     |

---

## Schema: `metrics.json`

```jsonc
{
  // Per-language metrics (one key per language, lowercase English name)
  "<language_name>": {
    "n_samples": 500,            // int: number of samples evaluated
    "wer": 38.01,                // float: Word Error Rate (%)
    "cer": 19.68,                // float: Character Error Rate (%)
    "wer_normalized": 34.07,     // float: WER after text normalization (%)
    "cer_normalized": 19.49,     // float: CER after text normalization (%)
    "empty_hypotheses": 0        // int: count of empty model outputs
  },
  // ... repeat for all 12 languages ...

  // Aggregate metrics
  "__overall__": {
    "n_samples": 6000,           // int: total samples (should be 6000)
    "wer": 35.16,                // float: micro-averaged WER
    "cer": 15.53,                // float: micro-averaged CER
    "wer_normalized": 29.97,     // float: micro-averaged normalized WER
    "cer_normalized": 14.48      // float: micro-averaged normalized CER
  },

  "__macro_avg__": {
    "n_languages": 12,           // int: number of languages
    "wer": 35.32,                // float: macro-averaged WER
    "cer": 15.49,                // float: macro-averaged CER
    "wer_normalized": 30.08,     // float: macro-averaged normalized WER
    "cer_normalized": 14.37      // float: macro-averaged normalized CER
  },

  // Run metadata
  "__meta__": {
    "checkpoint": "<full_path>",         // str: full path to checkpoint
    "checkpoint_name": "ckpt-20000",     // str: short checkpoint name
    "model_id": "gemma3n-e2b",           // str: model ID from registry above
    "model_type": "gemma3n-E2B-asr",     // str: model architecture type
    "dataset": "BayAreaBoys/indic-asr-benchmark-6k",  // str: HF dataset name
    "batch_size": 128,                   // int: inference batch size
    "inference_time_sec": 723.7,         // float: total inference wall time
    "total_audio_sec": 40354.46,         // float: total audio duration
    "rtf": 0.0179,                       // float: Real-Time Factor
    "timestamp": "2026-03-26T14:30:00Z", // str: ISO 8601 timestamp of run
    "gpu": "NVIDIA A100 80GB",           // str: GPU used (optional)
    "framework": "transformers"          // str: inference framework (optional)
  }
}
```

### Required Fields

**Per-language**: `n_samples`, `wer`, `cer`, `wer_normalized`, `cer_normalized`, `empty_hypotheses`
**__overall__**: `n_samples`, `wer`, `cer`, `wer_normalized`, `cer_normalized`
**__macro_avg__**: `n_languages`, `wer`, `cer`, `wer_normalized`, `cer_normalized`
**__meta__**: `checkpoint_name`, `model_id`, `dataset`, `inference_time_sec`, `total_audio_sec`, `rtf`, `timestamp`

---

## Schema: `sample_analysis.json`

An array of per-sample prediction objects:

```jsonc
[
  {
    "id": "as_0000",                    // str: unique sample ID
    "language": "assamese",             // str: language name (lowercase)
    "reference": "<ground truth text>", // str: reference transcription
    "hypothesis": "<model output>",     // str: model prediction
    "detected_language": "Assamese",    // str: language detected by model (optional)
    "ref_normalized": "<normalized>",   // str: normalized reference (optional)
    "hyp_normalized": "<normalized>"    // str: normalized hypothesis (optional)
  }
  // ... one entry per sample (6000 total) ...
]
```

---

## Agent Instructions

### For ALL agents (Qwen, Gemma, Parakeet)

1. **Run benchmarks** on the full `BayAreaBoys/indic-asr-benchmark-6k` dataset (6000 samples, 12 languages, 500 each).

2. **Output directory**: Place results in:
   ```
   /home/ubuntu/training/benchmark_outputs/<model_id>/<checkpoint_name>/
   ```
   - `model_id`: One of `qwen3-asr`, `gemma3n-e2b`, `parakeet-1.1b`
   - `checkpoint_name`: e.g. `ckpt-24000`, `ckpt-72000`, `baseline`

3. **Generate two files** per checkpoint:
   - `metrics.json` — conforming to the metrics schema above
   - `sample_analysis.json` — conforming to the sample analysis schema above

4. **CRITICAL**: The `__meta__.model_id` field MUST match the directory model_id exactly.

5. **CRITICAL**: The `__meta__.timestamp` MUST be set to the ISO 8601 UTC time when the benchmark run completed.

6. **Text normalization**: Apply Unicode NFKD normalization, lowercase, strip punctuation for `*_normalized` fields. Keep raw WER/CER computed on original text.

7. **Metrics computation**:
   - WER/CER: Use `jiwer` library or equivalent
   - `__overall__`: Micro-average (concatenate all samples, compute once)
   - `__macro_avg__`: Average per-language metrics

### Model-Specific Notes

#### Qwen3-ASR Agent
- Checkpoints: `ckpt-24000`, `ckpt-72000`, `ckpt-100000`
- Checkpoint paths: `/home/ubuntu/training/checkpoints/qwen3-asr-ckpt-{N}`
- Supports batch_size=32 with vLLM
- Set `model_id: "qwen3-asr"` in `__meta__`

#### Gemma3n-E2B Agent
- Checkpoints: `ckpt-10000`, `ckpt-20000`
- Checkpoint paths: `/home/ubuntu/training/checkpoints/gemma3n-e2b-ckpt-{N}`
- Set `model_id: "gemma3n-e2b"` in `__meta__`

#### Parakeet-1.1B Agent
- No checkpoints yet — use `baseline` as checkpoint_name
- Set `model_id: "parakeet-1.1b"` in `__meta__`
- When checkpoints become available, follow same naming convention

---

## Dashboard Data Flow

```
[Benchmark Scripts] --> metrics.json + sample_analysis.json
                            |
                            v
              /home/ubuntu/training/benchmark_outputs/
                            |
                            v
              [REST API / Static file serve]
                            |
                            v
                    [Next.js Dashboard]
```

The dashboard reads from `/home/ubuntu/training/benchmark_outputs/` via a local API route. Agents place files there; the dashboard picks them up automatically.

### API Endpoints (served by Next.js API routes)

```
GET /api/models                           → list of models with checkpoint info
GET /api/models/:modelId/checkpoints      → list checkpoints for a model
GET /api/metrics/:modelId/:checkpoint     → metrics.json content
GET /api/samples/:modelId/:checkpoint     → sample_analysis.json content
GET /api/compare?models=...&checkpoints=... → cross-model/checkpoint comparison
```

---

## Migrating Existing Results

Existing results in `benchmark_results/` and `benchmark_results_gemma3n/` should be copied to the new canonical location. The dashboard migration script handles this automatically.


File created successfully at: /home/ubuntu/training/benchmark_schema/BENCHMARK_SCHEMA.md

# ASR Benchmark Schema & Agent Instructions

## Overview

This document defines the **canonical JSON schema** for all ASR benchmark outputs across all models. Every agent producing benchmark results MUST conform to this schema so the dashboard can ingest results uniformly.

---

## Models & Directory Layout

```
/home/ubuntu/training/benchmark_outputs/
  qwen3-asr/
    ckpt-24000/
      metrics.json
      sample_analysis.json
    ckpt-72000/
      metrics.json
      sample_analysis.json
    ckpt-100000/
      metrics.json
      sample_analysis.json
  gemma3n-e2b/
    ckpt-10000/
      metrics.json
      sample_analysis.json
    ckpt-20000/
      metrics.json
      sample_analysis.json
  parakeet-1.1b/
    baseline/
      metrics.json
      sample_analysis.json
```

### Model Registry

| Model ID              | Display Name                       | Has Checkpoints | Notes                     |
|-----------------------|------------------------------------|-----------------|---------------------------|
| `qwen3-asr`          | Qwen3-ASR-1.7B                    | Yes             | ckpt-24000, 72000, 100000 |
| `gemma3n-e2b`        | Gemma-3n-E2B-ASR                  | Yes             | ckpt-10000, 20000         |
| `parakeet-1.1b`      | Parakeet-1.1B-Language-Guided     | No (yet)        | Use `baseline` as folder  |

### Languages (12 Indic + English)

| Language   | Code | Source Dataset          | Samples |
|------------|------|-------------------------|---------|
| assamese   | as   | IndicVoices (valid)     | 500     |
| bengali    | bn   | Kathbath (test_known)   | 500     |
| english    | en   | Svarah                  | 500     |
| gujarati   | gu   | Kathbath (test_known)   | 500     |
| hindi      | hi   | Kathbath (test_known)   | 500     |
| kannada    | kn   | Kathbath (test_known)   | 500     |
| malayalam  | ml   | Kathbath (test_known)   | 500     |
| marathi    | mr   | Kathbath (test_known)   | 500     |
| odia       | or   | Kathbath (test_known)   | 500     |
| punjabi    | pa   | Kathbath (test_known)   | 500     |
| tamil      | ta   | Kathbath (test_known)   | 500     |
| telugu     | te   | Kathbath (test_known)   | 500     |

---

## Schema: `metrics.json`

```jsonc
{
  // Per-language metrics (one key per language, lowercase English name)
  "<language_name>": {
    "n_samples": 500,            // int: number of samples evaluated
    "wer": 38.01,                // float: Word Error Rate (%)
    "cer": 19.68,                // float: Character Error Rate (%)
    "wer_normalized": 34.07,     // float: WER after text normalization (%)
    "cer_normalized": 19.49,     // float: CER after text normalization (%)
    "empty_hypotheses": 0        // int: count of empty model outputs
  },
  // ... repeat for all 12 languages ...

  // Aggregate metrics
  "__overall__": {
    "n_samples": 6000,           // int: total samples (should be 6000)
    "wer": 35.16,                // float: micro-averaged WER
    "cer": 15.53,                // float: micro-averaged CER
    "wer_normalized": 29.97,     // float: micro-averaged normalized WER
    "cer_normalized": 14.48      // float: micro-averaged normalized CER
  },

  "__macro_avg__": {
    "n_languages": 12,           // int: number of languages
    "wer": 35.32,                // float: macro-averaged WER
    "cer": 15.49,                // float: macro-averaged CER
    "wer_normalized": 30.08,     // float: macro-averaged normalized WER
    "cer_normalized": 14.37      // float: macro-averaged normalized CER
  },

  // Run metadata
  "__meta__": {
    "checkpoint": "<full_path>",         // str: full path to checkpoint
    "checkpoint_name": "ckpt-20000",     // str: short checkpoint name
    "model_id": "gemma3n-e2b",           // str: model ID from registry above
    "model_type": "gemma3n-E2B-asr",     // str: model architecture type
    "dataset": "BayAreaBoys/indic-asr-benchmark-6k",  // str: HF dataset name
    "batch_size": 128,                   // int: inference batch size
    "inference_time_sec": 723.7,         // float: total inference wall time
    "total_audio_sec": 40354.46,         // float: total audio duration
    "rtf": 0.0179,                       // float: Real-Time Factor
    "timestamp": "2026-03-26T14:30:00Z", // str: ISO 8601 timestamp of run
    "gpu": "NVIDIA A100 80GB",           // str: GPU used (optional)
    "framework": "transformers"          // str: inference framework (optional)
  }
}
```

### Required Fields

**Per-language**: `n_samples`, `wer`, `cer`, `wer_normalized`, `cer_normalized`, `empty_hypotheses`
**__overall__**: `n_samples`, `wer`, `cer`, `wer_normalized`, `cer_normalized`
**__macro_avg__**: `n_languages`, `wer`, `cer`, `wer_normalized`, `cer_normalized`
**__meta__**: `checkpoint_name`, `model_id`, `dataset`, `inference_time_sec`, `total_audio_sec`, `rtf`, `timestamp`

---

## Schema: `sample_analysis.json`

An array of per-sample prediction objects:

```jsonc
[
  {
    "id": "as_0000",                    // str: unique sample ID
    "language": "assamese",             // str: language name (lowercase)
    "reference": "<ground truth text>", // str: reference transcription
    "hypothesis": "<model output>",     // str: model prediction
    "detected_language": "Assamese",    // str: language detected by model (optional)
    "ref_normalized": "<normalized>",   // str: normalized reference (optional)
    "hyp_normalized": "<normalized>"    // str: normalized hypothesis (optional)
  }
  // ... one entry per sample (6000 total) ...
]
```

---

## Agent Instructions

### For ALL agents (Qwen, Gemma, Parakeet)

1. **Run benchmarks** on the full `BayAreaBoys/indic-asr-benchmark-6k` dataset (6000 samples, 12 languages, 500 each).

2. **Output directory**: Place results in:
   ```
   /home/ubuntu/training/benchmark_outputs/<model_id>/<checkpoint_name>/
   ```
   - `model_id`: One of `qwen3-asr`, `gemma3n-e2b`, `parakeet-1.1b`
   - `checkpoint_name`: e.g. `ckpt-24000`, `ckpt-72000`, `baseline`

3. **Generate two files** per checkpoint:
   - `metrics.json` — conforming to the metrics schema above
   - `sample_analysis.json` — conforming to the sample analysis schema above

4. **CRITICAL**: The `__meta__.model_id` field MUST match the directory model_id exactly.

5. **CRITICAL**: The `__meta__.timestamp` MUST be set to the ISO 8601 UTC time when the benchmark run completed.

6. **Text normalization**: Apply Unicode NFKD normalization, lowercase, strip punctuation for `*_normalized` fields. Keep raw WER/CER computed on original text.

7. **Metrics computation**:
   - WER/CER: Use `jiwer` library or equivalent
   - `__overall__`: Micro-average (concatenate all samples, compute once)
   - `__macro_avg__`: Average per-language metrics

### Model-Specific Notes

#### Qwen3-ASR Agent
- Checkpoints: `ckpt-24000`, `ckpt-72000`, `ckpt-100000`
- Checkpoint paths: `/home/ubuntu/training/checkpoints/qwen3-asr-ckpt-{N}`
- Supports batch_size=32 with vLLM
- Set `model_id: "qwen3-asr"` in `__meta__`

#### Gemma3n-E2B Agent
- Checkpoints: `ckpt-10000`, `ckpt-20000`
- Checkpoint paths: `/home/ubuntu/training/checkpoints/gemma3n-e2b-ckpt-{N}`
- Set `model_id: "gemma3n-e2b"` in `__meta__`

#### Parakeet-1.1B Agent
- No checkpoints yet — use `baseline` as checkpoint_name
- Set `model_id: "parakeet-1.1b"` in `__meta__`
- When checkpoints become available, follow same naming convention

---

## Dashboard Data Flow

```
[Benchmark Scripts] --> metrics.json + sample_analysis.json
                            |
                            v
              /home/ubuntu/training/benchmark_outputs/
                            |
                            v
              [REST API / Static file serve]
                            |
                            v
                    [Next.js Dashboard]
```

The dashboard reads from `/home/ubuntu/training/benchmark_outputs/` via a local API route. Agents place files there; the dashboard picks them up automatically.

### API Endpoints (served by Next.js API routes)

```
GET /api/models                           → list of models with checkpoint info
GET /api/models/:modelId/checkpoints      → list checkpoints for a model
GET /api/metrics/:modelId/:checkpoint     → metrics.json content
GET /api/samples/:modelId/:checkpoint     → sample_analysis.json content
GET /api/compare?models=...&checkpoints=... → cross-model/checkpoint comparison
```

---

## Migrating Existing Results

Existing results in `benchmark_results/` and `benchmark_results_gemma3n/` should be copied to the new canonical location. The dashboard migration script handles this automatically.


Now let me migrate existing results to the canonical directory and set up the Next.js project.


Bash

Create canonical benchmark output directories


(Bash completed with no output)


# Copy existing qwen results
for ckpt in ckpt-24000 ckpt-72000; do
  if [ -f "/home/ubuntu/training/benchmark_results/$ckpt/metrics.json" ]; then
    cp "/home/ubuntu/training/benchmark_results/$ckpt/metrics.json" "/home/ubuntu/training/benchmark_outputs/qwen3-asr/$ckpt/metrics.json"
  fi
  if [ -f "/home/ubuntu/training/benchmark_results/$ckpt/sample_analysis.json" ]; then
    cp "/home/ubuntu/training/benchmark_results/$ckpt/sample_analysis.json" "/home/ubuntu/training/benchmark_outputs/qwen3-asr/$ckpt/sample_analysis.json"
  fi
done

# Copy existing gemma results
for ckpt in ckpt-10000 ckpt-20000; do
  if [ -f "/home/ubuntu/training/benchmark_results_gemma3n/$ckpt/metrics.json" ]; then
    cp "/home/ubuntu/training/benchmark_results_gemma3n/$ckpt/metrics.json" "/home/ubuntu/training/benchmark_outputs/gemma3n-e2b/$ckpt/metrics.json"
  fi
  if [ -f "/home/ubuntu/training/benchmark_results_gemma3n/$ckpt/sample_analysis.json" ]; then
    cp "/home/ubuntu/training/benchmark_results_gemma3n/$ckpt/sample_analysis.json" "/home/ubuntu/training/benchmark_outputs/gemma3n-e2b/$ckpt/sample_analysis.json"
  fi
done

echo "Migration complete"
ls -la /home/ubuntu/training/benchmark_outputs/qwen3-asr/ckpt-24000/
ls -la /home/ubuntu/training/benchmark_outputs/gemma3n-e2b/ckpt-20000/

Migrate existing benchmark results to canonical directory


Migration complete
total 60
drwxrwxr-x 2 ubuntu ubuntu  4096 Mar 26 03:57 .
drwxrwxr-x 5 ubuntu ubuntu  4096 Mar 26 03:57 ..
-rw-rw-r-- 1 ubuntu ubuntu  2534 Mar 26 03:57 metrics.json
-rw-rw-r-- 1 ubuntu ubuntu 49087 Mar 26 03:57 sample_analysis.json
total 60
drwxrwxr-x 2 ubuntu ubuntu  4096 Mar 26 03:57 .
drwxrwxr-x 4 ubuntu ubuntu  4096 Mar 26 03:57 ..
-rw-rw-r-- 1 ubuntu ubuntu  2543 Mar 26 03:57 metrics.json
-rw-rw-r-- 1 ubuntu ubuntu 46627 Mar 26 03:57 sample_analysis.json


Now I need to add `model_id` to the existing metrics files so they conform to the schema. Let me also patch the existing metrics to include `model_id` and `timestamp`.


cd /home/ubuntu/training/benchmark_outputs

# Add model_id to qwen metrics
for ckpt in qwen3-asr/ckpt-24000 qwen3-asr/ckpt-72000; do
  if [ -f "$ckpt/metrics.json" ]; then
    python3 -c "
import json
with open('$ckpt/metrics.json') as f:
    d = json.load(f)
d['__meta__']['model_id'] = 'qwen3-asr'
if 'timestamp' not in d['__meta__']:
    d['__meta__']['timestamp'] = '2026-03-25T00:00:00Z'
with open('$ckpt/metrics.json', 'w') as f:
    json.dump(d, f, indent=2)
"
  fi
done

# Add model_id to gemma metrics
for ckpt in gemma3n-e2b/ckpt-10000 gemma3n-e2b/ckpt-20000; do
  if [ -f "$ckpt/metrics.json" ]; then
    python3 -c "
import json
with open('$ckpt/metrics.json') as f:
    d = json.load(f)
d['__meta__']['model_id'] = 'gemma3n-e2b'
if 'timestamp' not in d['__meta__']:
    d['__meta__']['timestamp'] = '2026-03-25T00:00:00Z'
with open('$ckpt/metrics.json', 'w') as f:
    json.dump(d, f, indent=2)
"
  fi
done

echo "Patched all metrics files"

Patch existing metrics with model_id and timestamp


Patched all metrics files


Updated task #1 status


Updated task #2 status


Now let me build the Next.js dashboard. I'll set it up with everything needed.


cd /home/ubuntu/training && npx create-next-app@latest asr-benchmark-dashboard --typescript --tailwind --eslint --app --src-dir --no-import-alias --use-npm 2>&1 | tail -5

Create Next.js project


? Would you like to use React Compiler? › No / Yesnpm notice
npm notice New major version of npm available! 10.8.2 -> 11.12.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.12.0
npm notice To update run: npm install -g npm@11.12.0
npm notice


cd /home/ubuntu/training/asr-benchmark-dashboard && npm install recharts 2>&1 | tail -3

Install recharts for graphs


Error: Exit code 1
/bin/bash: line 1: cd: /home/ubuntu/training/asr-benchmark-dashboard: No such file or directory


ls /home/ubuntu/training/ | grep -i asr-bench

Check if dashboard dir exists


The create-next-app seems to have failed (likely due to the interactive prompt). Let me do it manually.


npx create-next-app@latest asr-benchmark-dashboard --typescript --tailwind --eslint --app --src-dir --no-import-alias --use-npm --yes 2>&1 | tail -10

Create Next.js project non-interactively


found 0 vulnerabilities

Generating route types...
✓ Types generated successfully

Initialized a git repository.

Success! Created asr-benchmark-dashboard at /home/ubuntu/training/asr-benchmark-dashboard


cd /home/ubuntu/training/asr-benchmark-dashboard && npm install recharts 2>&1 | tail -3

Install recharts


  run `npm fund` for details

found 0 vulnerabilities


Good. Now let me read the default layout/page files to understand the structure, then build everything.


     1→import type { Metadata } from "next";
     2→import { Geist, Geist_Mono } from "next/font/google";
     3→import "./globals.css";
     4→
     5→const geistSans = Geist({
     6→  variable: "--font-geist-sans",
     7→  subsets: ["latin"],
     8→});
     9→
    10→const geistMono = Geist_Mono({
    11→  variable: "--font-geist-mono",
    12→  subsets: ["latin"],
    13→});
    14→
    15→export const metadata: Metadata = {
    16→  title: "Create Next App",
    17→  description: "Generated by create next app",
    18→};
    19→
    20→export default function RootLayout({
    21→  children,
    22→}: Readonly<{
    23→  children: React.ReactNode;
    24→}>) {
    25→  return (
    26→    <html
    27→      lang="en"
    28→      className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
    29→    >
    30→      <body className="min-h-full flex flex-col">{children}</body>
    31→    </html>
    32→  );
    33→}
    34→

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html
      lang="en"
      className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
    >
      <body className="min-h-full flex flex-col">{children}</body>
    </html>
  );
}


Error: File does not exist. Note: your current working directory is /home/ubuntu/training/asr-benchmark-dashboard.


     1→@import "tailwindcss";
     2→
     3→:root {
     4→  --background: #ffffff;
     5→  --foreground: #171717;
     6→}
     7→
     8→@theme inline {
     9→  --color-background: var(--background);
    10→  --color-foreground: var(--foreground);
    11→  --font-sans: var(--font-geist-sans);
    12→  --font-mono: var(--font-geist-mono);
    13→}
    14→
    15→@media (prefers-color-scheme: dark) {
    16→  :root {
    17→    --background: #0a0a0a;
    18→    --foreground: #ededed;
    19→  }
    20→}
    21→
    22→body {
    23→  background: var(--background);
    24→  color: var(--foreground);
    25→  font-family: Arial, Helvetica, sans-serif;
    26→}
    27→

@import "tailwindcss";

:root {
  --background: #ffffff;
  --foreground: #171717;
}

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  background: var(--background);
  color: var(--foreground);
  font-family: Arial, Helvetica, sans-serif;
}


ls /home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/ 2>/dev/null || echo "no docs dir"

Check Next.js docs


01-app
02-pages
03-architecture
04-community
index.md


List app docs


01-getting-started
02-guides
03-api-reference
04-glossary.md
index.md


Agent

Check Next.js API route docs

Prompt:
Read these files from /home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/01-app/:
1. Look in 03-api-reference for how to define API routes (route.ts/route handlers)
2. Look in 01-getting-started for any breaking changes or new patterns
3. Check if there are deprecation notices about tailwind config

Give me the key patterns for:
- API route handlers (GET/POST)
- Any CSS/tailwind changes
- Any layout/page changes from standard Next.js


Read these files from /home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/01-app/:
1. Look in 03-api-reference for how to define API routes (route.ts/route handlers)
2. Look in 01-getting-started for any breaking changes or new patterns
3. Check if there are deprecation notices about tailwind config

Give me the key patterns for:
- API route handlers (GET/POST)
- Any CSS/tailwind changes
- Any layout/page changes from standard Next.js

Prompt:
Read these files from /home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/01-app/:
1. Look in 03-api-reference for how to define API routes (route.ts/route handlers)
2. Look in 01-getting-started for any breaking changes or new patterns
3. Check if there are deprecation notices about tailwind config

Give me the key patterns for:
- API route handlers (GET/POST)
- Any CSS/tailwind changes
- Any layout/page changes from standard Next.js


Bash

find /home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs -type d -name "01-app" 2>/dev/null | head -5


ls -la /home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/01-app/ 2>/dev/null | head -20


total 48
drwxrwxr-x 5 ubuntu ubuntu  4096 Mar 26 03:58 .
drwxrwxr-x 6 ubuntu ubuntu  4096 Mar 26 03:58 ..
drwxrwxr-x 2 ubuntu ubuntu  4096 Mar 26 03:58 01-getting-started
drwxrwxr-x 5 ubuntu ubuntu  4096 Mar 26 03:58 02-guides
drwxrwxr-x 8 ubuntu ubuntu  4096 Mar 26 03:58 03-api-reference
-rw-rw-r-- 1 ubuntu ubuntu 20875 Mar 26 03:58 04-glossary.md
-rw-rw-r-- 1 ubuntu ubuntu   891 Mar 26 03:58 index.md


find /home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/01-app/01-getting-started -type f -name "*.md" | head -10


find /home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/01-app/03-api-reference -type f -name "*route*" | head -10


     1→---
     2→title: route.js
     3→description: API reference for the route.js special file.
     4→---
     5→
     6→Route Handlers allow you to create custom request handlers for a given route using the Web [Request](https://developer.mozilla.org/docs/Web/API/Request) and [Response](https://developer.mozilla.org/docs/Web/API/Response) APIs.
     7→
     8→```ts filename="route.ts" switcher
     9→export async function GET() {
    10→  return Response.json({ message: 'Hello World' })
    11→}
    12→```
    13→
    14→```js filename="route.js" switcher
    15→export async function GET() {
    16→  return Response.json({ message: 'Hello World' })
    17→}
    18→```
    19→
    20→## Reference
    21→
    22→### HTTP Methods
    23→
    24→A **route** file allows you to create custom request handlers for a given route. The following [HTTP methods](https://developer.mozilla.org/docs/Web/HTTP/Methods) are supported: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, and `OPTIONS`.
    25→
    26→```ts filename="route.ts" switcher
    27→export async function GET(request: Request) {}
    28→
    29→export async function HEAD(request: Request) {}
    30→
    31→export async function POST(request: Request) {}
    32→
    33→export async function PUT(request: Request) {}
    34→
    35→export async function DELETE(request: Request) {}
    36→
    37→export async function PATCH(request: Request) {}
    38→
    39→// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS` and set the appropriate Response `Allow` header depending on the other methods defined in the Route Handler.
    40→export async function OPTIONS(request: Request) {}
    41→```
    42→
    43→```js filename="route.js" switcher
    44→export async function GET(request) {}
    45→
    46→export async function HEAD(request) {}
    47→
    48→export async function POST(request) {}
    49→
    50→export async function PUT(request) {}
    51→
    52→export async function DELETE(request) {}
    53→
    54→export async function PATCH(request) {}
    55→
    56→// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS` and set the appropriate Response `Allow` header depending on the other methods defined in the Route Handler.
    57→export async function OPTIONS(request) {}
    58→```
    59→
    60→### Parameters
    61→
    62→#### `request` (optional)
    63→
    64→The `request` object is a [NextRequest](/docs/app/api-reference/functions/next-request) object, which is an extension of the Web [Request](https://developer.mozilla.org/docs/Web/API/Request) API. `NextRequest` gives you further control over the incoming request, including easily accessing `cookies` and an extended, parsed, URL object `nextUrl`.
    65→
    66→```ts filename="route.ts" switcher
    67→import type { NextRequest } from 'next/server'
    68→
    69→export async function GET(request: NextRequest) {
    70→  const url = request.nextUrl
    71→}
    72→```
    73→
    74→```js filename="route.js" switcher
    75→export async function GET(request) {
    76→  const url = request.nextUrl
    77→}
    78→```
    79→
    80→#### `context` (optional)
    81→
    82→- **`params`**: a promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) for the current route.
    83→
    84→```ts filename="app/dashboard/[team]/route.ts" switcher
    85→export async function GET(
    86→  request: Request,
    87→  { params }: { params: Promise<{ team: string }> }
    88→) {
    89→  const { team } = await params
    90→}
    91→```
    92→
    93→```js filename="app/dashboard/[team]/route.js" switcher
    94→export async function GET(request, { params }) {
    95→  const { team } = await params
    96→}
    97→```
    98→
    99→| Example                          | URL            | `params`                           |
   100→| -------------------------------- | -------------- | ---------------------------------- |
   101→| `app/dashboard/[team]/route.js`  | `/dashboard/1` | `Promise<{ team: '1' }>`           |
   102→| `app/shop/[tag]/[item]/route.js` | `/shop/1/2`    | `Promise<{ tag: '1', item: '2' }>` |
   103→| `app/blog/[...slug]/route.js`    | `/blog/1/2`    | `Promise<{ slug: ['1', '2'] }>`    |
   104→
   105→### Route Context Helper
   106→
   107→You can type the Route Handler context using `RouteContext` to get strongly typed `params` from a route literal. `RouteContext` is a globally available helper.
   108→
   109→```ts filename="app/users/[id]/route.ts"
   110→import type { NextRequest } from 'next/server'
   111→
   112→export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) {
   113→  const { id } = await ctx.params
   114→  return Response.json({ id })
   115→}
   116→```
   117→
   118→> **Good to know**
   119→>
   120→> - Types are generated during `next dev`, `next build` or `next typegen`.
   121→> - After type generation, the `RouteContext` helper is globally available. It doesn't need to be imported.
   122→
   123→## Examples
   124→
   125→### Cookies
   126→
   127→You can read or set cookies with [`cookies`](/docs/app/api-reference/functions/cookies) from `next/headers`.
   128→
   129→```ts filename="route.ts" switcher
   130→import { cookies } from 'next/headers'
   131→
   132→export async function GET(request: NextRequest) {
   133→  const cookieStore = await cookies()
   134→
   135→  const a = cookieStore.get('a')
   136→  const b = cookieStore.set('b', '1')
   137→  const c = cookieStore.delete('c')
   138→}
   139→```
   140→
   141→```js filename="route.js" switcher
   142→import { cookies } from 'next/headers'
   143→
   144→export async function GET(request) {
   145→  const cookieStore = await cookies()
   146→
   147→  const a = cookieStore.get('a')
   148→  const b = cookieStore.set('b', '1')
   149→  const c = cookieStore.delete('c')
   150→}
   151→```
   152→
   153→Alternatively, you can return a new `Response` using the [`Set-Cookie`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie) header.
   154→
   155→```ts filename="app/api/route.ts" switcher
   156→import { cookies } from 'next/headers'
   157→
   158→export async function GET(request: Request) {
   159→  const cookieStore = await cookies()
   160→  const token = cookieStore.get('token')
   161→
   162→  return new Response('Hello, Next.js!', {
   163→    status: 200,
   164→    headers: { 'Set-Cookie': `token=${token.value}` },
   165→  })
   166→}
   167→```
   168→
   169→```js filename="app/api/route.js" switcher
   170→import { cookies } from 'next/headers'
   171→
   172→export async function GET(request) {
   173→  const cookieStore = await cookies()
   174→  const token = cookieStore.get('token')
   175→
   176→  return new Response('Hello, Next.js!', {
   177→    status: 200,
   178→    headers: { 'Set-Cookie': `token=${token.value}` },
   179→  })
   180→}
   181→```
   182→
   183→You can also use the underlying Web APIs to read cookies from the request ([`NextRequest`](/docs/app/api-reference/functions/next-request)):
   184→
   185→```ts filename="app/api/route.ts" switcher
   186→import { type NextRequest } from 'next/server'
   187→
   188→export async function GET(request: NextRequest) {
   189→  const token = request.cookies.get('token')
   190→}
   191→```
   192→
   193→```js filename="app/api/route.js" switcher
   194→export async function GET(request) {
   195→  const token = request.cookies.get('token')
   196→}
   197→```
   198→
   199→### Headers
   200→
   201→You can read headers with [`headers`](/docs/app/api-reference/functions/headers) from `next/headers`.
   202→
   203→```ts filename="route.ts" switcher
   204→import { headers } from 'next/headers'
   205→import type { NextRequest } from 'next/server'
   206→
   207→export async function GET(request: NextRequest) {
   208→  const headersList = await headers()
   209→  const referer = headersList.get('referer')
   210→}
   211→```
   212→
   213→```js filename="route.js" switcher
   214→import { headers } from 'next/headers'
   215→
   216→export async function GET(request) {
   217→  const headersList = await headers()
   218→  const referer = headersList.get('referer')
   219→}
   220→```
   221→
   222→This `headers` instance is read-only. To set headers, you need to return a new `Response` with new `headers`.
   223→
   224→```ts filename="app/api/route.ts" switcher
   225→import { headers } from 'next/headers'
   226→
   227→export async function GET(request: Request) {
   228→  const headersList = await headers()
   229→  const referer = headersList.get('referer')
   230→
   231→  return new Response('Hello, Next.js!', {
   232→    status: 200,
   233→    headers: { referer: referer },
   234→  })
   235→}
   236→```
   237→
   238→```js filename="app/api/route.js" switcher
   239→import { headers } from 'next/headers'
   240→
   241→export async function GET(request) {
   242→  const headersList = await headers()
   243→  const referer = headersList.get('referer')
   244→
   245→  return new Response('Hello, Next.js!', {
   246→    status: 200,
   247→    headers: { referer: referer },
   248→  })
   249→}
   250→```
   251→
   252→You can also use the underlying Web APIs to read headers from the request ([`NextRequest`](/docs/app/api-reference/functions/next-request)):
   253→
   254→```ts filename="app/api/route.ts" switcher
   255→import { type NextRequest } from 'next/server'
   256→
   257→export async function GET(request: NextRequest) {
   258→  const requestHeaders = new Headers(request.headers)
   259→}
   260→```
   261→
   262→```js filename="app/api/route.js" switcher
   263→export async function GET(request) {
   264→  const requestHeaders = new Headers(request.headers)
   265→}
   266→```
   267→
   268→### Revalidating Cached Data
   269→
   270→You can [revalidate cached data](/docs/app/guides/incremental-static-regeneration) using the `revalidate` route segment config option.
   271→
   272→```ts filename="app/posts/route.ts" switcher
   273→export const revalidate = 60
   274→
   275→export async function GET() {
   276→  const data = await fetch('https://api.vercel.app/blog')
   277→  const posts = await data.json()
   278→
   279→  return Response.json(posts)
   280→}
   281→```
   282→
   283→```js filename="app/posts/route.js" switcher
   284→export const revalidate = 60
   285→
   286→export async function GET() {
   287→  const data = await fetch('https://api.vercel.app/blog')
   288→  const posts = await data.json()
   289→
   290→  return Response.json(posts)
   291→}
   292→```
   293→
   294→### Redirects
   295→
   296→```ts filename="app/api/route.ts" switcher
   297→import { redirect } from 'next/navigation'
   298→
   299→export async function GET(request: Request) {
   300→  redirect('https://nextjs.org/')
   301→}
   302→```
   303→
   304→```js filename="app/api/route.js" switcher
   305→import { redirect } from 'next/navigation'
   306→
   307→export async function GET(request) {
   308→  redirect('https://nextjs.org/')
   309→}
   310→```
   311→
   312→### Dynamic Route Segments
   313→
   314→Route Handlers can use [Dynamic Segments](/docs/app/api-reference/file-conventions/dynamic-routes) to create request handlers from dynamic data.
   315→
   316→```ts filename="app/items/[slug]/route.ts" switcher
   317→export async function GET(
   318→  request: Request,
   319→  { params }: { params: Promise<{ slug: string }> }
   320→) {
   321→  const { slug } = await params // 'a', 'b', or 'c'
   322→}
   323→```
   324→
   325→```js filename="app/items/[slug]/route.js" switcher
   326→export async function GET(request, { params }) {
   327→  const { slug } = await params // 'a', 'b', or 'c'
   328→}
   329→```
   330→
   331→| Route                       | Example URL | `params`                 |
   332→| --------------------------- | ----------- | ------------------------ |
   333→| `app/items/[slug]/route.js` | `/items/a`  | `Promise<{ slug: 'a' }>` |
   334→| `app/items/[slug]/route.js` | `/items/b`  | `Promise<{ slug: 'b' }>` |
   335→| `app/items/[slug]/route.js` | `/items/c`  | `Promise<{ slug: 'c' }>` |
   336→
   337→#### Static Generation with `generateStaticParams`
   338→
   339→You can use [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) with dynamic Route Handlers to statically generate responses at build time for specified params, while handling other params dynamically at request time.
   340→
   341→When using [Cache Components](/docs/app/getting-started/caching), you can combine `generateStaticParams` with `use cache` to enable data caching for both prerendered and runtime params.
   342→
   343→See the [generateStaticParams with Route Handlers](/docs/app/api-reference/functions/generate-static-params#with-route-handlers) documentation for examples and details.
   344→
   345→### URL Query Parameters
   346→
   347→The request object passed to the Route Handler is a `NextRequest` instance, which includes [some additional convenience methods](/docs/app/api-reference/functions/next-request#nexturl), such as those for more easily handling query parameters.
   348→
   349→```ts filename="app/api/search/route.ts" switcher
   350→import { type NextRequest } from 'next/server'
   351→
   352→export function GET(request: NextRequest) {
   353→  const searchParams = request.nextUrl.searchParams
   354→  const query = searchParams.get('query')
   355→  // query is "hello" for /api/search?query=hello
   356→}
   357→```
   358→
   359→```js filename="app/api/search/route.js" switcher
   360→export function GET(request) {
   361→  const searchParams = request.nextUrl.searchParams
   362→  const query = searchParams.get('query')
   363→  // query is "hello" for /api/search?query=hello
   364→}
   365→```
   366→
   367→### Streaming
   368→
   369→Streaming is commonly used in combination with Large Language Models (LLMs), such as OpenAI, for AI-generated content. Learn more about the [AI SDK](https://sdk.vercel.ai/docs/introduction).
   370→
   371→```ts filename="app/api/chat/route.ts" switcher
   372→import { openai } from '@ai-sdk/openai'
   373→import { StreamingTextResponse, streamText } from 'ai'
   374→
   375→export async function POST(req: Request) {
   376→  const { messages } = await req.json()
   377→  const result = await streamText({
   378→    model: openai('gpt-4-turbo'),
   379→    messages,
   380→  })
   381→
   382→  return new StreamingTextResponse(result.toAIStream())
   383→}
   384→```
   385→
   386→```js filename="app/api/chat/route.js" switcher
   387→import { openai } from '@ai-sdk/openai'
   388→import { StreamingTextResponse, streamText } from 'ai'
   389→
   390→export async function POST(req) {
   391→  const { messages } = await req.json()
   392→  const result = await streamText({
   393→    model: openai('gpt-4-turbo'),
   394→    messages,
   395→  })
   396→
   397→  return new StreamingTextResponse(result.toAIStream())
   398→}
   399→```
   400→
   401→These abstractions use the Web APIs to create a stream. You can also use the underlying Web APIs directly.
   402→
   403→```ts filename="app/api/route.ts" switcher
   404→// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
   405→function iteratorToStream(iterator: any) {
   406→  return new ReadableStream({
   407→    async pull(controller) {
   408→      const { value, done } = await iterator.next()
   409→
   410→      if (done) {
   411→        controller.close()
   412→      } else {
   413→        controller.enqueue(value)
   414→      }
   415→    },
   416→  })
   417→}
   418→
   419→function sleep(time: number) {
   420→  return new Promise((resolve) => {
   421→    setTimeout(resolve, time)
   422→  })
   423→}
   424→
   425→const encoder = new TextEncoder()
   426→
   427→async function* makeIterator() {
   428→  yield encoder.encode('<p>One</p>')
   429→  await sleep(200)
   430→  yield encoder.encode('<p>Two</p>')
   431→  await sleep(200)
   432→  yield encoder.encode('<p>Three</p>')
   433→}
   434→
   435→export async function GET() {
   436→  const iterator = makeIterator()
   437→  const stream = iteratorToStream(iterator)
   438→
   439→  return new Response(stream)
   440→}
   441→```
   442→
   443→```js filename="app/api/route.js" switcher
   444→// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
   445→function iteratorToStream(iterator) {
   446→  return new ReadableStream({
   447→    async pull(controller) {
   448→      const { value, done } = await iterator.next()
   449→
   450→      if (done) {
   451→        controller.close()
   452→      } else {
   453→        controller.enqueue(value)
   454→      }
   455→    },
   456→  })
   457→}
   458→
   459→function sleep(time) {
   460→  return new Promise((resolve) => {
   461→    setTimeout(resolve, time)
   462→  })
   463→}
   464→
   465→const encoder = new TextEncoder()
   466→
   467→async function* makeIterator() {
   468→  yield encoder.encode('<p>One</p>')
   469→  await sleep(200)
   470→  yield encoder.encode('<p>Two</p>')
   471→  await sleep(200)
   472→  yield encoder.encode('<p>Three</p>')
   473→}
   474→
   475→export async function GET() {
   476→  const iterator = makeIterator()
   477→  const stream = iteratorToStream(iterator)
   478→
   479→  return new Response(stream)
   480→}
   481→```
   482→
   483→### Request Body
   484→
   485→You can read the `Request` body using the standard Web API methods:
   486→
   487→```ts filename="app/items/route.ts" switcher
   488→export async function POST(request: Request) {
   489→  const res = await request.json()
   490→  return Response.json({ res })
   491→}
   492→```
   493→
   494→```js filename="app/items/route.js" switcher
   495→export async function POST(request) {
   496→  const res = await request.json()
   497→  return Response.json({ res })
   498→}
   499→```
   500→
   501→### Request Body FormData
   502→
   503→You can read the `FormData` using the `request.formData()` function:
   504→
   505→```ts filename="app/items/route.ts" switcher
   506→export async function POST(request: Request) {
   507→  const formData = await request.formData()
   508→  const name = formData.get('name')
   509→  const email = formData.get('email')
   510→  return Response.json({ name, email })
   511→}
   512→```
   513→
   514→```js filename="app/items/route.js" switcher
   515→export async function POST(request) {
   516→  const formData = await request.formData()
   517→  const name = formData.get('name')
   518→  const email = formData.get('email')
   519→  return Response.json({ name, email })
   520→}
   521→```
   522→
   523→Since `formData` data are all strings, you may want to use [`zod-form-data`](https://www.npmjs.com/zod-form-data) to validate the request and retrieve data in the format you prefer (e.g. `number`).
   524→
   525→### CORS
   526→
   527→You can set CORS headers for a specific Route Handler using the standard Web API methods:
   528→
   529→```ts filename="app/api/route.ts" switcher
   530→export async function GET(request: Request) {
   531→  return new Response('Hello, Next.js!', {
   532→    status: 200,
   533→    headers: {
   534→      'Access-Control-Allow-Origin': '*',
   535→      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
   536→      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
   537→    },
   538→  })
   539→}
   540→```
   541→
   542→```js filename="app/api/route.js" switcher
   543→export async function GET(request) {
   544→  return new Response('Hello, Next.js!', {
   545→    status: 200,
   546→    headers: {
   547→      'Access-Control-Allow-Origin': '*',
   548→      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
   549→      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
   550→    },
   551→  })
   552→}
   553→```
   554→
   555→> **Good to know**:
   556→>
   557→> - To add CORS headers to multiple Route Handlers, you can use [Proxy](/docs/app/api-reference/file-conventions/proxy#cors) or the [`next.config.js` file](/docs/app/api-reference/config/next-config-js/headers#cors).
   558→
   559→### Webhooks
   560→
   561→You can use a Route Handler to receive webhooks from third-party services:
   562→
   563→```ts filename="app/api/route.ts" switcher
   564→export async function POST(request: Request) {
   565→  try {
   566→    const text = await request.text()
   567→    // Process the webhook payload
   568→  } catch (error) {
   569→    return new Response(`Webhook error: ${error.message}`, {
   570→      status: 400,
   571→    })
   572→  }
   573→
   574→  return new Response('Success!', {
   575→    status: 200,
   576→  })
   577→}
   578→```
   579→
   580→```js filename="app/api/route.js" switcher
   581→export async function POST(request) {
   582→  try {
   583→    const text = await request.text()
   584→    // Process the webhook payload
   585→  } catch (error) {
   586→    return new Response(`Webhook error: ${error.message}`, {
   587→      status: 400,
   588→    })
   589→  }
   590→
   591→  return new Response('Success!', {
   592→    status: 200,
   593→  })
   594→}
   595→```
   596→
   597→Notably, unlike API Routes with the Pages Router, you do not need to use `bodyParser` to use any additional configuration.
   598→
   599→### Non-UI Responses
   600→
   601→You can use Route Handlers to return non-UI content. Note that [`sitemap.xml`](/docs/app/api-reference/file-conventions/metadata/sitemap#generating-a-sitemap-using-code-js-ts), [`robots.txt`](/docs/app/api-reference/file-conventions/metadata/robots#generate-a-robots-file), [`app icons`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx), and [open graph images](/docs/app/api-reference/file-conventions/metadata/opengraph-image) all have built-in support.
   602→
   603→```ts filename="app/rss.xml/route.ts" switcher
   604→export async function GET() {
   605→  return new Response(
   606→    `<?xml version="1.0" encoding="UTF-8" ?>
   607→<rss version="2.0">
   608→
   609→<channel>
   610→  <title>Next.js Documentation</title>
   611→  <link>https://nextjs.org/docs</link>
   612→  <description>The React Framework for the Web</description>
   613→</channel>
   614→
   615→</rss>`,
   616→    {
   617→      headers: {
   618→        'Content-Type': 'text/xml',
   619→      },
   620→    }
   621→  )
   622→}
   623→```
   624→
   625→```js filename="app/rss.xml/route.js" switcher
   626→export async function GET() {
   627→  return new Response(`<?xml version="1.0" encoding="UTF-8" ?>
   628→<rss version="2.0">
   629→
   630→<channel>
   631→  <title>Next.js Documentation</title>
   632→  <link>https://nextjs.org/docs</link>
   633→  <description>The React Framework for the Web</description>
   634→</channel>
   635→
   636→</rss>`)
   637→}
   638→```
   639→
   640→### Segment Config Options
   641→
   642→Route Handlers use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) as pages and layouts.
   643→
   644→```ts filename="app/items/route.ts" switcher
   645→export const dynamic = 'auto'
   646→export const dynamicParams = true
   647→export const revalidate = false
   648→export const fetchCache = 'auto'
   649→export const runtime = 'nodejs'
   650→export const preferredRegion = 'auto'
   651→```
   652→
   653→```js filename="app/items/route.js" switcher
   654→export const dynamic = 'auto'
   655→export const dynamicParams = true
   656→export const revalidate = false
   657→export const fetchCache = 'auto'
   658→export const runtime = 'nodejs'
   659→export const preferredRegion = 'auto'
   660→```
   661→
   662→See the [API reference](/docs/app/api-reference/file-conventions/route-segment-config) for more details.
   663→
   664→## Version History
   665→
   666→| Version      | Changes                                                                                |
   667→| ------------ | ---------------------------------------------------------------------------------------------------- |
   668→| `v15.0.0-RC` | `context.params` is now a promise. A [codemod](/docs/app/guides/upgrading/codemods#150) is available |
   669→| `v15.0.0-RC` | The default caching for `GET` handlers was changed from static to dynamic                            |
   670→| `v13.2.0`    | Route Handlers are introduced.                                                                       |
   671→


     1→---
     2→title: Route Handlers
     3→nav_title: Route Handlers
     4→description: Learn how to use Route Handlers
     5→related:
     6→  title: API Reference
     7→  description: Learn more about Route Handlers
     8→  links:
     9→    - app/api-reference/file-conventions/route
    10→    - app/guides/backend-for-frontend
    11→---
    12→
    13→## Route Handlers
    14→
    15→Route Handlers allow you to create custom request handlers for a given route using the Web [Request](https://developer.mozilla.org/docs/Web/API/Request) and [Response](https://developer.mozilla.org/docs/Web/API/Response) APIs.
    16→
    17→<Image
    18→  alt="Route.js Special File"
    19→  srcLight="/docs/light/route-special-file.png"
    20→  srcDark="/docs/dark/route-special-file.png"
    21→  width="1600"
    22→  height="444"
    23→/>
    24→
    25→> **Good to know**: Route Handlers are only available inside the `app` directory. They are the equivalent of [API Routes](/docs/pages/building-your-application/routing/api-routes) inside the `pages` directory meaning you **do not** need to use API Routes and Route Handlers together.
    26→
    27→### Convention
    28→
    29→Route Handlers are defined in a [`route.js|ts` file](/docs/app/api-reference/file-conventions/route) inside the `app` directory:
    30→
    31→```ts filename="app/api/route.ts" switcher
    32→export async function GET(request: Request) {}
    33→```
    34→
    35→```js filename="app/api/route.js" switcher
    36→export async function GET(request) {}
    37→```
    38→
    39→Route Handlers can be nested anywhere inside the `app` directory, similar to `page.js` and `layout.js`. But there **cannot** be a `route.js` file at the same route segment level as `page.js`.
    40→
    41→### Supported HTTP Methods
    42→
    43→The following [HTTP methods](https://developer.mozilla.org/docs/Web/HTTP/Methods) are supported: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, and `OPTIONS`. If an unsupported method is called, Next.js will return a `405 Method Not Allowed` response.
    44→
    45→### Extended `NextRequest` and `NextResponse` APIs
    46→
    47→In addition to supporting the native [Request](https://developer.mozilla.org/docs/Web/API/Request) and [Response](https://developer.mozilla.org/docs/Web/API/Response) APIs, Next.js extends them with [`NextRequest`](/docs/app/api-reference/functions/next-request) and [`NextResponse`](/docs/app/api-reference/functions/next-response) to provide convenient helpers for advanced use cases.
    48→
    49→### Caching
    50→
    51→Route Handlers are not cached by default. You can, however, opt into caching for `GET` methods. Other supported HTTP methods are **not** cached. To cache a `GET` method, use a [route config option](/docs/app/guides/caching-without-cache-components#dynamic) such as `export const dynamic = 'force-static'` in your Route Handler file.
    52→
    53→```ts filename="app/items/route.ts" switcher
    54→export const dynamic = 'force-static'
    55→
    56→export async function GET() {
    57→  const res = await fetch('https://data.mongodb-api.com/...', {
    58→    headers: {
    59→      'Content-Type': 'application/json',
    60→      'API-Key': process.env.DATA_API_KEY,
    61→    },
    62→  })
    63→  const data = await res.json()
    64→
    65→  return Response.json({ data })
    66→}
    67→```
    68→
    69→```js filename="app/items/route.js" switcher
    70→export const dynamic = 'force-static'
    71→
    72→export async function GET() {
    73→  const res = await fetch('https://data.mongodb-api.com/...', {
    74→    headers: {
    75→      'Content-Type': 'application/json',
    76→      'API-Key': process.env.DATA_API_KEY,
    77→    },
    78→  })
    79→  const data = await res.json()
    80→
    81→  return Response.json({ data })
    82→}
    83→```
    84→
    85→> **Good to know**: Other supported HTTP methods are **not** cached, even if they are placed alongside a `GET` method that is cached, in the same file.
    86→
    87→#### With Cache Components
    88→
    89→When [Cache Components](/docs/app/getting-started/caching) is enabled, `GET` Route Handlers follow the same model as normal UI routes in your application. They run at request time by default, can be prerendered when they don't access uncached or runtime data, and you can use `use cache` to include uncached data in the static response.
    90→
    91→**Static example** - doesn't access uncached or runtime data, so it will be prerendered at build time:
    92→
    93→```tsx filename="app/api/project-info/route.ts"
    94→export async function GET() {
    95→  return Response.json({
    96→    projectName: 'Next.js',
    97→  })
    98→}
    99→```
   100→
   101→**Dynamic example** - accesses non-deterministic operations. During the build, prerendering stops when `Math.random()` is called, deferring to request-time rendering:
   102→
   103→```tsx filename="app/api/random-number/route.ts"
   104→export async function GET() {
   105→  return Response.json({
   106→    randomNumber: Math.random(),
   107→  })
   108→}
   109→```
   110→
   111→**Runtime data example** - accesses request-specific data. Prerendering terminates when runtime APIs like `headers()` are called:
   112→
   113→```tsx filename="app/api/user-agent/route.ts"
   114→import { headers } from 'next/headers'
   115→
   116→export async function GET() {
   117→  const headersList = await headers()
   118→  const userAgent = headersList.get('user-agent')
   119→
   120→  return Response.json({ userAgent })
   121→}
   122→```
   123→
   124→> **Good to know**: Prerendering stops if the `GET` handler accesses network requests, database queries, async file system operations, request object properties (like `req.url`, `request.headers`, `request.cookies`, `request.body`), runtime APIs like [`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), [`connection()`](/docs/app/api-reference/functions/connection), or non-deterministic operations.
   125→
   126→**Cached example** - accesses uncached data (database query) but caches it with `use cache`, allowing it to be included in the prerendered response:
   127→
   128→```tsx filename="app/api/products/route.ts"
   129→import { cacheLife } from 'next/cache'
   130→
   131→export async function GET() {
   132→  const products = await getProducts()
   133→  return Response.json(products)
   134→}
   135→
   136→async function getProducts() {
   137→  'use cache'
   138→  cacheLife('hours')
   139→
   140→  return await db.query('SELECT * FROM products')
   141→}
   142→```
   143→
   144→> **Good to know**: `use cache` cannot be used directly inside a Route Handler body; extract it to a helper function. Cached responses revalidate according to `cacheLife` when a new request arrives.
   145→
   146→### Special Route Handlers
   147→
   148→Special Route Handlers like [`sitemap.ts`](/docs/app/api-reference/file-conventions/metadata/sitemap), [`opengraph-image.tsx`](/docs/app/api-reference/file-conventions/metadata/opengraph-image), and [`icon.tsx`](/docs/app/api-reference/file-conventions/metadata/app-icons), and other [metadata files](/docs/app/api-reference/file-conventions/metadata) remain static by default unless they use Request-time APIs or dynamic config options.
   149→
   150→### Route Resolution
   151→
   152→You can consider a `route` the lowest level routing primitive.
   153→
   154→- They **do not** participate in layouts or client-side navigations like `page`.
   155→- There **cannot** be a `route.js` file at the same route as `page.js`.
   156→
   157→| Page                 | Route              | Result                       |
   158→| -------------------- | ------------------ | ---------------------------- |
   159→| `app/page.js`        | `app/route.js`     | <Cross size={18} /> Conflict |
   160→| `app/page.js`        | `app/api/route.js` | <Check size={18} /> Valid    |
   161→| `app/[user]/page.js` | `app/api/route.js` | <Check size={18} /> Valid    |
   162→
   163→Each `route.js` or `page.js` file takes over all HTTP verbs for that route.
   164→
   165→```ts filename="app/page.ts" switcher
   166→export default function Page() {
   167→  return <h1>Hello, Next.js!</h1>
   168→}
   169→
   170→// Conflict
   171→// `app/route.ts`
   172→export async function POST(request: Request) {}
   173→```
   174→
   175→```js filename="app/page.js" switcher
   176→export default function Page() {
   177→  return <h1>Hello, Next.js!</h1>
   178→}
   179→
   180→// Conflict
   181→// `app/route.js`
   182→export async function POST(request) {}
   183→```
   184→
   185→Read more about how Route Handlers [complement your frontend application](/docs/app/guides/backend-for-frontend), or explore the Route Handlers [API Reference](/docs/app/api-reference/file-conventions/route).
   186→
   187→### Route Context Helper
   188→
   189→In TypeScript, you can type the `context` parameter for Route Handlers with the globally available [`RouteContext`](/docs/app/api-reference/file-conventions/route#route-context-helper) helper:
   190→
   191→```ts filename="app/users/[id]/route.ts" switcher
   192→import type { NextRequest } from 'next/server'
   193→
   194→export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) {
   195→  const { id } = await ctx.params
   196→  return Response.json({ id })
   197→}
   198→```
   199→
   200→> **Good to know**
   201→>
   202→> - Types are generated during `next dev`, `next build` or `next typegen`.
   203→


     1→---
     2→title: CSS
     3→description: Learn about the different ways to add CSS to your application, including Tailwind CSS, CSS Modules, Global CSS, and more.
     4→related:
     5→  title: Next Steps
     6→  description: Learn more about the alternatives ways you can use CSS in your application.
     7→  links:
     8→    - app/guides/tailwind-v3-css
     9→    - app/guides/sass
    10→    - app/guides/css-in-js
    11→---
    12→
    13→Next.js provides several ways to style your application using CSS, including:
    14→
    15→- [Tailwind CSS](#tailwind-css)
    16→- [CSS Modules](#css-modules)
    17→- [Global CSS](#global-css)
    18→- [External Stylesheets](#external-stylesheets)
    19→- [Sass](/docs/app/guides/sass)
    20→- [CSS-in-JS](/docs/app/guides/css-in-js)
    21→
    22→## Tailwind CSS
    23→
    24→[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework that provides low-level utility classes to build custom designs.
    25→
    26→<AppOnly>
    27→
    28→Install Tailwind CSS:
    29→
    30→```bash package="pnpm"
    31→pnpm add -D tailwindcss @tailwindcss/postcss
    32→```
    33→
    34→```bash package="npm"
    35→npm install -D tailwindcss @tailwindcss/postcss
    36→```
    37→
    38→```bash package="yarn"
    39→yarn add -D tailwindcss @tailwindcss/postcss
    40→```
    41→
    42→```bash package="bun"
    43→bun add -D tailwindcss @tailwindcss/postcss
    44→```
    45→
    46→Add the PostCSS plugin to your `postcss.config.mjs` file:
    47→
    48→```js filename="postcss.config.mjs"
    49→export default {
    50→  plugins: {
    51→    '@tailwindcss/postcss': {},
    52→  },
    53→}
    54→```
    55→
    56→Import Tailwind in your global CSS file:
    57→
    58→```css filename="app/globals.css"
    59→@import 'tailwindcss';
    60→```
    61→
    62→Import the CSS file in your root layout:
    63→
    64→```tsx filename="app/layout.tsx" switcher
    65→import './globals.css'
    66→
    67→export default function RootLayout({
    68→  children,
    69→}: {
    70→  children: React.ReactNode
    71→}) {
    72→  return (
    73→    <html lang="en">
    74→      <body>{children}</body>
    75→    </html>
    76→  )
    77→}
    78→```
    79→
    80→```jsx filename="app/layout.js" switcher
    81→import './globals.css'
    82→
    83→export default function RootLayout({ children }) {
    84→  return (
    85→    <html lang="en">
    86→      <body>{children}</body>
    87→    </html>
    88→  )
    89→}
    90→```
    91→
    92→Now you can start using Tailwind's utility classes in your application:
    93→
    94→```tsx filename="app/page.tsx" switcher
    95→export default function Page() {
    96→  return (
    97→    <main className="flex min-h-screen flex-col items-center justify-between p-24">
    98→      <h1 className="text-4xl font-bold">Welcome to Next.js!</h1>
    99→    </main>
   100→  )
   101→}
   102→```
   103→
   104→```jsx filename="app/page.js" switcher
   105→export default function Page() {
   106→  return (
   107→    <main className="flex min-h-screen flex-col items-center justify-between p-24">
   108→      <h1 className="text-4xl font-bold">Welcome to Next.js!</h1>
   109→    </main>
   110→  )
   111→}
   112→```
   113→
   114→</AppOnly>
   115→
   116→<PagesOnly>
   117→
   118→Install Tailwind CSS:
   119→
   120→```bash package="pnpm"
   121→pnpm add -D tailwindcss @tailwindcss/postcss
   122→```
   123→
   124→```bash package="npm"
   125→npm install -D tailwindcss @tailwindcss/postcss
   126→```
   127→
   128→```bash package="yarn"
   129→yarn add -D tailwindcss @tailwindcss/postcss
   130→```
   131→
   132→```bash package="bun"
   133→bun add -D tailwindcss @tailwindcss/postcss
   134→```
   135→
   136→Add the PostCSS plugin to your `postcss.config.mjs` file:
   137→
   138→```js filename="postcss.config.mjs"
   139→export default {
   140→  plugins: {
   141→    '@tailwindcss/postcss': {},
   142→  },
   143→}
   144→```
   145→
   146→Import Tailwind in your global CSS file:
   147→
   148→```css filename="styles/globals.css"
   149→@import 'tailwindcss';
   150→```
   151→
   152→Import the CSS file in your `pages/_app.js` file:
   153→
   154→```jsx filename="pages/_app.js"
   155→import '@/styles/globals.css'
   156→
   157→export default function MyApp({ Component, pageProps }) {
   158→  return <Component {...pageProps} />
   159→}
   160→```
   161→
   162→Now you can start using Tailwind's utility classes in your application:
   163→
   164→```tsx filename="pages/index.tsx" switcher
   165→export default function Home() {
   166→  return (
   167→    <main className="flex min-h-screen flex-col items-center justify-between p-24">
   168→      <h1 className="text-4xl font-bold">Welcome to Next.js!</h1>
   169→    </main>
   170→  )
   171→}
   172→```
   173→
   174→```jsx filename="pages/index.js" switcher
   175→export default function Home() {
   176→  return (
   177→    <main className="flex min-h-screen flex-col items-center justify-between p-24">
   178→      <h1 className="text-4xl font-bold">Welcome to Next.js!</h1>
   179→    </main>
   180→  )
   181→}
   182→```
   183→
   184→</PagesOnly>
   185→
   186→> **Good to know:** If you need broader browser support for very old browsers, see the [Tailwind CSS v3 setup instructions](/docs/app/guides/tailwind-v3-css).
   187→
   188→## CSS Modules
   189→
   190→CSS Modules locally scope CSS by generating unique class names. This allows you to use the same class in different files without worrying about naming collisions.
   191→
   192→<AppOnly>
   193→
   194→To start using CSS Modules, create a new file with the extension `.module.css` and import it into any component inside the `app` directory:
   195→
   196→```css filename="app/blog/blog.module.css"
   197→.blog {
   198→  padding: 24px;
   199→}
   200→```
   201→
   202→```tsx filename="app/blog/page.tsx" switcher
   203→import styles from './blog.module.css'
   204→
   205→export default function Page() {
   206→  return <main className={styles.blog}></main>
   207→}
   208→```
   209→
   210→```jsx filename="app/blog/page.js" switcher
   211→import styles from './blog.module.css'
   212→
   213→export default function Page() {
   214→  return <main className={styles.blog}></main>
   215→}
   216→```
   217→
   218→</AppOnly>
   219→
   220→<PagesOnly>
   221→
   222→To start using CSS Modules, create a new file with the extension `.module.css` and import it into any component inside the `pages` directory:
   223→
   224→```css filename="/styles/blog.module.css"
   225→.blog {
   226→  padding: 24px;
   227→}
   228→```
   229→
   230→```tsx filename="pages/blog/index.tsx" switcher
   231→import styles from './blog.module.css'
   232→
   233→export default function Page() {
   234→  return <main className={styles.blog}></main>
   235→}
   236→```
   237→
   238→```jsx filename="pages/blog/index.js" switcher
   239→import styles from './blog.module.css'
   240→
   241→export default function Page() {
   242→  return <main className={styles.blog}></main>
   243→}
   244→```
   245→
   246→</PagesOnly>
   247→
   248→## Global CSS
   249→
   250→You can use global CSS to apply styles across your application.
   251→
   252→<AppOnly>
   253→
   254→Create a `app/global.css` file and import it in the root layout to apply the styles to **every route** in your application:
   255→
   256→```css filename="app/global.css"
   257→body {
   258→  padding: 20px 20px 60px;
   259→  max-width: 680px;
   260→  margin: 0 auto;
   261→}
   262→```
   263→
   264→```tsx filename="app/layout.tsx" switcher
   265→// These styles apply to every route in the application
   266→import './global.css'
   267→
   268→export default function RootLayout({
   269→  children,
   270→}: {
   271→  children: React.ReactNode
   272→}) {
   273→  return (
   274→    <html lang="en">
   275→      <body>{children}</body>
   276→    </html>
   277→  )
   278→}
   279→```
   280→
   281→```jsx filename="app/layout.js" switcher
   282→// These styles apply to every route in the application
   283→import './global.css'
   284→
   285→export default function RootLayout({ children }) {
   286→  return (
   287→    <html lang="en">
   288→      <body>{children}</body>
   289→    </html>
   290→  )
   291→}
   292→```
   293→
   294→> **Good to know:** Global styles can be imported into any layout, page, or component inside the `app` directory. However, since Next.js uses React's built-in support for stylesheets to integrate with Suspense, this currently does not remove stylesheets as you navigate between routes which can lead to conflicts. We recommend using global styles for _truly_ global CSS (like Tailwind's base styles), [Tailwind CSS](#tailwind-css) for component styling, and [CSS Modules](#css-modules) for custom scoped CSS when needed.
   295→
   296→</AppOnly>
   297→
   298→<PagesOnly>
   299→
   300→Import the stylesheet in the `pages/_app.js` file to apply the styles to **every route** in your application:
   301→
   302→```tsx filename="pages/_app.js"
   303→import '@/styles/global.css'
   304→
   305→export default function MyApp({ Component, pageProps }) {
   306→  return <Component {...pageProps} />
   307→}
   308→```
   309→
   310→Due to the global nature of stylesheets, and to avoid conflicts, you should import them inside [`pages/_app.js`](/docs/pages/building-your-application/routing/custom-app).
   311→
   312→</PagesOnly>
   313→
   314→## External stylesheets
   315→
   316→<AppOnly>
   317→
   318→Stylesheets published by external packages can be imported anywhere in the `app` directory, including colocated components:
   319→
   320→```tsx filename="app/layout.tsx" switcher
   321→import 'bootstrap/dist/css/bootstrap.css'
   322→
   323→export default function RootLayout({
   324→  children,
   325→}: {
   326→  children: React.ReactNode
   327→}) {
   328→  return (
   329→    <html lang="en">
   330→      <body className="container">{children}</body>
   331→    </html>
   332→  )
   333→}
   334→```
   335→
   336→```jsx filename="app/layout.js" switcher
   337→import 'bootstrap/dist/css/bootstrap.css'
   338→
   339→export default function RootLayout({ children }) {
   340→  return (
   341→    <html lang="en">
   342→      <body className="container">{children}</body>
   343→    </html>
   344→  )
   345→}
   346→```
   347→
   348→> **Good to know:** In React 19, `<link rel="stylesheet" href="..." />` can also be used. See the [React `link` documentation](https://react.dev/reference/react-dom/components/link) for more information.
   349→
   350→</AppOnly>
   351→
   352→<PagesOnly>
   353→
   354→Next.js allows you to import CSS files from a JavaScript file. This is possible because Next.js extends the concept of [`import`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import) beyond JavaScript.
   355→
   356→### Import styles from `node_modules`
   357→
   358→Since Next.js **9.5.4**, importing a CSS file from `node_modules` is permitted anywhere in your application.
   359→
   360→For global stylesheets, like `bootstrap` or `nprogress`, you should import the file inside `pages/_app.js`. For example:
   361→
   362→```jsx filename="pages/_app.js"
   363→import 'bootstrap/dist/css/bootstrap.css'
   364→
   365→export default function MyApp({ Component, pageProps }) {
   366→  return <Component {...pageProps} />
   367→}
   368→```
   369→
   370→To import CSS required by a third-party component, you can do so in your component. For example:
   371→
   372→```jsx filename="components/example-dialog.js"
   373→import { useState } from 'react'
   374→import { Dialog } from '@reach/dialog'
   375→import VisuallyHidden from '@reach/visually-hidden'
   376→import '@reach/dialog/styles.css'
   377→
   378→function ExampleDialog(props) {
   379→  const [showDialog, setShowDialog] = useState(false)
   380→  const open = () => setShowDialog(true)
   381→  const close = () => setShowDialog(false)
   382→
   383→  return (
   384→    <div>
   385→      <button onClick={open}>Open Dialog</button>
   386→      <Dialog isOpen={showDialog} onDismiss={close}>
   387→        <button className="close-button" onClick={close}>
   388→          <VisuallyHidden>Close</VisuallyHidden>
   389→          <span aria-hidden>×</span>
   390→        </button>
   391→        <p>Hello there. I am a dialog</p>
   392→      </Dialog>
   393→    </div>
   394→  )
   395→}
   396→```
   397→
   398→</PagesOnly>
   399→
   400→## Ordering and Merging
   401→
   402→Next.js optimizes CSS during production builds by automatically chunking (merging) stylesheets. The **order of your CSS** depends on the **order you import styles in your code**.
   403→
   404→For example, `base-button.module.css` will be ordered before `page.module.css` since `<BaseButton>` is imported before `page.module.css`:
   405→
   406→```tsx filename="page.tsx" switcher
   407→import { BaseButton } from './base-button'
   408→import styles from './page.module.css'
   409→
   410→export default function Page() {
   411→  return <BaseButton className={styles.primary} />
   412→}
   413→```
   414→
   415→```jsx filename="page.js" switcher
   416→import { BaseButton } from './base-button'
   417→import styles from './page.module.css'
   418→
   419→export default function Page() {
   420→  return <BaseButton className={styles.primary} />
   421→}
   422→```
   423→
   424→```tsx filename="base-button.tsx" switcher
   425→import styles from './base-button.module.css'
   426→
   427→export function BaseButton() {
   428→  return <button className={styles.primary} />
   429→}
   430→```
   431→
   432→```jsx filename="base-button.js" switcher
   433→import styles from './base-button.module.css'
   434→
   435→export function BaseButton() {
   436→  return <button className={styles.primary} />
   437→}
   438→```
   439→
   440→### Recommendations
   441→
   442→To keep CSS ordering predictable:
   443→
   444→- Try to contain CSS imports to a single JavaScript or TypeScript entry file
   445→- Import global styles and Tailwind stylesheets in the root of your application.
   446→- **Use Tailwind CSS** for most styling needs as it covers common design patterns with utility classes.
   447→- Use CSS Modules for component-specific styles when Tailwind utilities aren't sufficient.
   448→- Use a consistent naming convention for your CSS modules. For example, using `<name>.module.css` over `<name>.tsx`.
   449→- Extract shared styles into shared components to avoid duplicate imports.
   450→- Turn off linters or formatters that auto-sort imports like ESLint’s [`sort-imports`](https://eslint.org/docs/latest/rules/sort-imports).
   451→- You can use the [`cssChunking`](/docs/app/api-reference/config/next-config-js/cssChunking) option in `next.config.js` to control how CSS is chunked.
   452→
   453→## Development vs Production
   454→
   455→- In development (`next dev`), CSS updates apply instantly with [Fast Refresh](/docs/architecture/fast-refresh).
   456→- In production (`next build`), all CSS files are automatically concatenated into **many minified and code-split** `.css` files, ensuring the minimal amount of CSS is loaded for a route.
   457→- CSS still loads with JavaScript disabled in production, but JavaScript is required in development for Fast Refresh.
   458→- CSS ordering can behave differently in development, always ensure to check the build (`next build`) to verify the final CSS order.
   459→


     1→---
     2→title: Layouts and Pages
     3→description: Learn how to create your first pages and layouts, and link between them with the Link component.
     4→related:
     5→  title: API Reference
     6→  description: Learn more about the features mentioned in this page by reading the API Reference.
     7→  links:
     8→    - app/getting-started/linking-and-navigating
     9→    - app/api-reference/file-conventions/layout
    10→    - app/api-reference/file-conventions/page
    11→    - app/api-reference/components/link
    12→    - app/api-reference/file-conventions/dynamic-routes
    13→---
    14→
    15→Next.js uses **file-system based routing**, meaning you can use folders and files to define routes. This page will guide you through how to create layouts and pages, and link between them.
    16→
    17→## Creating a page
    18→
    19→A **page** is UI that is rendered on a specific route. To create a page, add a [`page` file](/docs/app/api-reference/file-conventions/page) inside the `app` directory and default export a React component. For example, to create an index page (`/`):
    20→
    21→<Image
    22→  alt="page.js special file"
    23→  srcLight="/docs/light/page-special-file.png"
    24→  srcDark="/docs/dark/page-special-file.png"
    25→  width="1600"
    26→  height="282"
    27→/>
    28→
    29→```tsx filename="app/page.tsx" switcher
    30→export default function Page() {
    31→  return <h1>Hello Next.js!</h1>
    32→}
    33→```
    34→
    35→```jsx filename="app/page.js" switcher
    36→export default function Page() {
    37→  return <h1>Hello Next.js!</h1>
    38→}
    39→```
    40→
    41→## Creating a layout
    42→
    43→A layout is UI that is **shared** between multiple pages. On navigation, layouts preserve state, remain interactive, and do not rerender.
    44→
    45→You can define a layout by default exporting a React component from a [`layout` file](/docs/app/api-reference/file-conventions/layout). The component should accept a `children` prop which can be a page or another [layout](#nesting-layouts).
    46→
    47→For example, to create a layout that accepts your index page as child, add a `layout` file inside the `app` directory:
    48→
    49→<Image
    50→  alt="layout.js special file"
    51→  srcLight="/docs/light/layout-special-file.png"
    52→  srcDark="/docs/dark/layout-special-file.png"
    53→  width="1600"
    54→  height="363"
    55→/>
    56→
    57→```tsx filename="app/layout.tsx" switcher
    58→export default function DashboardLayout({
    59→  children,
    60→}: {
    61→  children: React.ReactNode
    62→}) {
    63→  return (
    64→    <html lang="en">
    65→      <body>
    66→        {/* Layout UI */}
    67→        {/* Place children where you want to render a page or nested layout */}
    68→        <main>{children}</main>
    69→      </body>
    70→    </html>
    71→  )
    72→}
    73→```
    74→
    75→```jsx filename="app/layout.js" switcher
    76→export default function DashboardLayout({ children }) {
    77→  return (
    78→    <html lang="en">
    79→      <body>
    80→        {/* Layout UI */}
    81→        {/* Place children where you want to render a page or nested layout */}
    82→        <main>{children}</main>
    83→      </body>
    84→    </html>
    85→  )
    86→}
    87→```
    88→
    89→The layout above is called a [root layout](/docs/app/api-reference/file-conventions/layout#root-layout) because it's defined at the root of the `app` directory. The root layout is **required** and must contain `html` and `body` tags.
    90→
    91→## Creating a nested route
    92→
    93→A nested route is a route composed of multiple URL segments. For example, the `/blog/[slug]` route is composed of three segments:
    94→
    95→- `/` (Root Segment)
    96→- `blog` (Segment)
    97→- `[slug]` (Leaf Segment)
    98→
    99→In Next.js:
   100→
   101→- **Folders** are used to define the route segments that map to URL segments.
   102→- **Files** (like `page` and `layout`) are used to create UI that is shown for a segment.
   103→
   104→To create nested routes, you can nest folders inside each other. For example, to add a route for `/blog`, create a folder called `blog` in the `app` directory. Then, to make `/blog` publicly accessible, add a `page.tsx` file:
   105→
   106→<Image
   107→  alt="File hierarchy showing blog folder and a page.js file"
   108→  srcLight="/docs/light/blog-nested-route.png"
   109→  srcDark="/docs/dark/blog-nested-route.png"
   110→  width="1600"
   111→  height="525"
   112→/>
   113→
   114→```tsx filename="app/blog/page.tsx" switcher
   115→// Dummy imports
   116→import { getPosts } from '@/lib/posts'
   117→import { Post } from '@/ui/post'
   118→
   119→export default async function Page() {
   120→  const posts = await getPosts()
   121→
   122→  return (
   123→    <ul>
   124→      {posts.map((post) => (
   125→        <Post key={post.id} post={post} />
   126→      ))}
   127→    </ul>
   128→  )
   129→}
   130→```
   131→
   132→```jsx filename="app/blog/page.js" switcher
   133→// Dummy imports
   134→import { getPosts } from '@/lib/posts'
   135→import { Post } from '@/ui/post'
   136→
   137→export default async function Page() {
   138→  const posts = await getPosts()
   139→
   140→  return (
   141→    <ul>
   142→      {posts.map((post) => (
   143→        <Post key={post.id} post={post} />
   144→      ))}
   145→    </ul>
   146→  )
   147→}
   148→```
   149→
   150→You can continue nesting folders to create nested routes. For example, to create a route for a specific blog post, create a new `[slug]` folder inside `blog` and add a `page` file:
   151→
   152→<Image
   153→  alt="File hierarchy showing blog folder with a nested slug folder and a page.js file"
   154→  srcLight="/docs/light/blog-post-nested-route.png"
   155→  srcDark="/docs/dark/blog-post-nested-route.png"
   156→  width="1600"
   157→  height="687"
   158→/>
   159→
   160→```tsx filename="app/blog/[slug]/page.tsx" switcher
   161→function generateStaticParams() {}
   162→
   163→export default function Page() {
   164→  return <h1>Hello, Blog Post Page!</h1>
   165→}
   166→```
   167→
   168→```jsx filename="app/blog/[slug]/page.js" switcher
   169→function generateStaticParams() {}
   170→
   171→export default function Page() {
   172→  return <h1>Hello, Blog Post Page!</h1>
   173→}
   174→```
   175→
   176→Wrapping a folder name in square brackets (e.g. `[slug]`) creates a [dynamic route segment](/docs/app/api-reference/file-conventions/dynamic-routes) which is used to generate multiple pages from data. e.g. blog posts, product pages, etc.
   177→
   178→## Nesting layouts
   179→
   180→By default, layouts in the folder hierarchy are also nested, which means they wrap child layouts via their `children` prop. You can nest layouts by adding `layout` inside specific route segments (folders).
   181→
   182→For example, to create a layout for the `/blog` route, add a new `layout` file inside the `blog` folder.
   183→
   184→<Image
   185→  alt="File hierarchy showing root layout wrapping the blog layout"
   186→  srcLight="/docs/light/nested-layouts.png"
   187→  srcDark="/docs/dark/nested-layouts.png"
   188→  width="1600"
   189→  height="768"
   190→/>
   191→
   192→```tsx filename="app/blog/layout.tsx" switcher
   193→export default function BlogLayout({
   194→  children,
   195→}: {
   196→  children: React.ReactNode
   197→}) {
   198→  return <section>{children}</section>
   199→}
   200→```
   201→
   202→```jsx filename="app/blog/layout.js" switcher
   203→export default function BlogLayout({ children }) {
   204→  return <section>{children}</section>
   205→}
   206→```
   207→
   208→If you were to combine the two layouts above, the root layout (`app/layout.js`) would wrap the blog layout (`app/blog/layout.js`), which would wrap the blog (`app/blog/page.js`) and blog post page (`app/blog/[slug]/page.js`).
   209→
   210→## Creating a dynamic segment
   211→
   212→[Dynamic segments](/docs/app/api-reference/file-conventions/dynamic-routes) allow you to create routes that are generated from data. For example, instead of manually creating a route for each individual blog post, you can create a dynamic segment to generate the routes based on blog post data.
   213→
   214→To create a dynamic segment, wrap the segment (folder) name in square brackets: `[segmentName]`. For example, in the `app/blog/[slug]/page.tsx` route, the `[slug]` is the dynamic segment.
   215→
   216→```tsx filename="app/blog/[slug]/page.tsx" switcher
   217→export default async function BlogPostPage({
   218→  params,
   219→}: {
   220→  params: Promise<{ slug: string }>
   221→}) {
   222→  const { slug } = await params
   223→  const post = await getPost(slug)
   224→
   225→  return (
   226→    <div>
   227→      <h1>{post.title}</h1>
   228→      <p>{post.content}</p>
   229→    </div>
   230→  )
   231→}
   232→```
   233→
   234→```jsx filename="app/blog/[slug]/page.js" switcher
   235→export default async function BlogPostPage({ params }) {
   236→  const { slug } = await params
   237→  const post = await getPost(slug)
   238→
   239→  return (
   240→    <div>
   241→      <h1>{post.title}</h1>
   242→      <p>{post.content}</p>
   243→    </div>
   244→  )
   245→}
   246→```
   247→
   248→Learn more about [Dynamic Segments](/docs/app/api-reference/file-conventions/dynamic-routes) and the [`params`](/docs/app/api-reference/file-conventions/page#params-optional) props.
   249→
   250→Nested [layouts within Dynamic Segments](/docs/app/api-reference/file-conventions/layout#params-optional), can also access the `params` props.
   251→
   252→## Rendering with search params
   253→
   254→In a Server Component **page**, you can access search parameters using the [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) prop:
   255→
   256→```tsx filename="app/page.tsx" switcher
   257→export default async function Page({
   258→  searchParams,
   259→}: {
   260→  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
   261→}) {
   262→  const filters = (await searchParams).filters
   263→}
   264→```
   265→
   266→```jsx filename="app/page.jsx" switcher
   267→export default async function Page({ searchParams }) {
   268→  const filters = (await searchParams).filters
   269→}
   270→```
   271→
   272→Using `searchParams` opts your page into [**dynamic rendering**](/docs/app/glossary#dynamic-rendering) because it requires an incoming request to read the search parameters from.
   273→
   274→Client Components can read search params using the [`useSearchParams`](/docs/app/api-reference/functions/use-search-params) hook.
   275→
   276→Learn more about `useSearchParams` in [prerendered](/docs/app/api-reference/functions/use-search-params#prerendering) and [dynamically rendered](/docs/app/api-reference/functions/use-search-params#dynamic-rendering) routes.
   277→
   278→### What to use and when
   279→
   280→- Use the `searchParams` prop when you need search parameters to **load data for the page** (e.g. pagination, filtering from a database).
   281→- Use `useSearchParams` when search parameters are used **only on the client** (e.g. filtering a list already loaded via props).
   282→- As a small optimization, you can use `new URLSearchParams(window.location.search)` in **callbacks or event handlers** to read search params without triggering re-renders.
   283→
   284→## Linking between pages
   285→
   286→You can use the [`<Link>` component](/docs/app/api-reference/components/link) to navigate between routes. `<Link>` is a built-in Next.js component that extends the HTML `<a>` tag to provide [prefetching](/docs/app/getting-started/linking-and-navigating#prefetching) and [client-side navigation](/docs/app/getting-started/linking-and-navigating#client-side-transitions).
   287→
   288→For example, to generate a list of blog posts, import `<Link>` from `next/link` and pass a `href` prop to the component:
   289→
   290→```tsx filename="app/ui/post.tsx" highlight={1,10} switcher
   291→import Link from 'next/link'
   292→
   293→export default async function Post({ post }) {
   294→  const posts = await getPosts()
   295→
   296→  return (
   297→    <ul>
   298→      {posts.map((post) => (
   299→        <li key={post.slug}>
   300→          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
   301→        </li>
   302→      ))}
   303→    </ul>
   304→  )
   305→}
   306→```
   307→
   308→```jsx filename="app/ui/post.js" highlight={1,10}  switcher
   309→import Link from 'next/link'
   310→
   311→export default async function Post({ post }) {
   312→  const posts = await getPosts()
   313→
   314→  return (
   315→    <ul>
   316→      {posts.map((post) => (
   317→        <li key={post.slug}>
   318→          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
   319→        </li>
   320→      ))}
   321→    </ul>
   322→  )
   323→}
   324→```
   325→
   326→> **Good to know**: `<Link>` is the primary way to navigate between routes in Next.js. You can also use the [`useRouter` hook](/docs/app/api-reference/functions/use-router) for more advanced navigation.
   327→
   328→## Route Props Helpers
   329→
   330→Next.js exposes utility types that infer `params` and named slots from your route structure:
   331→
   332→- [**PageProps**](/docs/app/api-reference/file-conventions/page#page-props-helper): Props for `page` components, including `params` and `searchParams`.
   333→- [**LayoutProps**](/docs/app/api-reference/file-conventions/layout#layout-props-helper): Props for `layout` components, including `children` and any named slots (e.g. folders like `@analytics`).
   334→
   335→These are globally available helpers, generated when running either `next dev`, `next build` or [`next typegen`](/docs/app/api-reference/cli/next#next-typegen-options).
   336→
   337→```tsx filename="app/blog/[slug]/page.tsx"
   338→export default async function Page(props: PageProps<'/blog/[slug]'>) {
   339→  const { slug } = await props.params
   340→  return <h1>Blog post: {slug}</h1>
   341→}
   342→```
   343→
   344→```tsx filename="app/dashboard/layout.tsx"
   345→export default function Layout(props: LayoutProps<'/dashboard'>) {
   346→  return (
   347→    <section>
   348→      {props.children}
   349→      {/* If you have app/dashboard/@analytics, it appears as a typed slot: */}
   350→      {/* {props.analytics} */}
   351→    </section>
   352→  )
   353→}
   354→```
   355→
   356→> **Good to know**
   357→>
   358→> - Static routes resolve `params` to `{}`.
   359→> - `PageProps`, `LayoutProps` are global helpers — no imports required.
   360→> - Types are generated during `next dev`, `next build` or `next typegen`.
   361→


Excellent! I've read all the key documentation files. Here are the critical patterns you need to know for this Next.js version:

## API Route Handlers (GET/POST)

**File Location:** `app/api/*/route.ts` or `route.js`

**Basic Pattern:**
```ts
export async function GET(request: Request) {
  return Response.json({ message: 'Hello World' })
}

export async function POST(request: Request) {
  const data = await request.json()
  return Response.json({ result: data })
}
```

**Supported Methods:** GET, HEAD, POST, PUT, DELETE, PATCH, OPTIONS

**Key Changes from Standard Next.js:**
- **params are now Promises** (v15.0.0-RC breaking change) - you must `await` them:
  ```ts
  export async function GET(request: Request, { params }: { params: Promise<{ team: string }> }) {
    const { team } = await params
  }
  ```
- **GET handlers are now DYNAMIC by default** (v15.0.0-RC) - they run at request time, not cached. Use `export const dynamic = 'force-static'` to opt into caching.

**Query Parameters:**
```ts
export function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams
  const query = searchParams.get('query')
}
```

**Cookies & Headers:**
```ts
import { cookies, headers } from 'next/headers'

export async function GET(request: NextRequest) {
  const cookieStore = await cookies()
  const headersList = await headers()
  const referer = headersList.get('referer')
}
```

**Request Body:**
```ts
export async function POST(request: Request) {
  const data = await request.json()
  // or for FormData:
  const formData = await request.formData()
  const name = formData.get('name')
}
```

**Route Context Helper (TypeScript):**
```ts
import type { NextRequest } from 'next/server'

export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) {
  const { id } = await ctx.params
  return Response.json({ id })
}
```

---

## CSS/Tailwind Changes

**Tailwind Installation (NEW v4):**
- Install: `npm install -D tailwindcss @tailwindcss/postcss`
- `postcss.config.mjs`:
  ```js
  export default {
    plugins: {
      '@tailwindcss/postcss': {},
    },
  }
  ```
- Import in global CSS: `@import 'tailwindcss';`

**NO `tailwind.config.js` configuration needed** - this is a breaking change from Tailwind v3. Configuration is now done via CSS.

**CSS Ordering:**
- Global styles + Tailwind in root layout
- CSS Modules for component-specific styles
- Import order matters - styles are merged in import order
- Recommendation: contain CSS imports to single entry file

**Global CSS Pattern:**
```tsx
// app/layout.tsx
import './globals.css'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
```

---

## Layout/Page Changes

**New Patterns (v15.0.0-RC):**

1. **params and searchParams are now Promises** - must await them:
   ```tsx
   export default async function Page({
     params,
     searchParams,
   }: {
     params: Promise<{ slug: string }>
     searchParams: Promise<{ [key: string]: string | string[] | undefined }>
   }) {
     const { slug } = await params
     const filters = (await searchParams).filters
   }
   ```

2. **Route Props Helpers (TypeScript)** - globally available, no imports needed:
   ```tsx
   // PageProps helper for pages
   export default async function Page(props: PageProps<'/blog/[slug]'>) {
     const { slug } = await props.params
   }

   // LayoutProps helper for layouts
   export default function Layout(props: LayoutProps<'/dashboard'>) {
     return <section>{props.children}</section>
   }
   ```

3. **File Structure:**
   - `route.js` at same level as `page.js` = conflict (cannot coexist)
   - Layouts nest automatically via `children` prop
   - Dynamic segments: `[slug]` or `[...slug]` for catch-all

**Dynamic Rendering:**
- Using `searchParams` opts page into dynamic rendering (requires incoming request)
- Use `searchParams` for data loading; use `useSearchParams` hook for client-side filtering only

---

## Key Deprecation Notes

From `AGENTS.md`: "This is NOT the Next.js you know" - read docs before coding because:
- Tailwind v3 `tailwind.config.js` is gone → use `@tailwindcss/postcss`
- GET route handlers are dynamic by default (not cached)
- params/searchParams are Promises (must await)

**Files:**
- `/home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/route.md`
- `/home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/01-app/01-getting-started/11-css.md`
- `/home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/01-app/01-getting-started/03-layouts-and-pages.md`

Prompt:
Read these files from /home/ubuntu/training/asr-benchmark-dashboard/node_modules/next/dist/docs/01-app/:
1. Look in 03-api-reference for how to define API routes (route.ts/route handlers)
2. Look in 01-getting-started for any breaking changes or new patterns
3. Check if there are deprecation notices about tailwind config

Give me the key patterns for:
- API route handlers (GET/POST)
- Any CSS/tailwind changes
- Any layout/page changes from standard Next.js


Good, key changes: params are Promises, Tailwind v4 (no config file), GET is dynamic by default. Let me now build the dashboard. I'll create all the files in parallel.


Write

@import "tailwindcss";

@theme inline {
  --color-background: #09090b;
  --color-foreground: #fafafa;
  --color-card: #18181b;
  --color-card-hover: #27272a;
  --color-border: #27272a;
  --color-border-light: #3f3f46;
  --color-muted: #71717a;
  --color-accent: #a1a1aa;
  --color-success: #22c55e;
  --color-danger: #ef4444;
  --color-warning: #eab308;
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}

body {
  background: var(--color-background);
  color: var(--color-foreground);
  font-family: var(--font-mono), monospace;
}

/* Custom scrollbar */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
::-webkit-scrollbar-track {
  background: var(--color-background);
}
::-webkit-scrollbar-thumb {
  background: var(--color-border-light);
  border-radius: 3px;
}

/* Recharts overrides */
.recharts-cartesian-grid line {
  stroke: #27272a !important;
}
.recharts-text {
  fill: #a1a1aa !important;
  font-family: var(--font-mono), monospace !important;
  font-size: 11px !important;
}
.recharts-tooltip-wrapper .recharts-default-tooltip {
  background: #18181b !important;
  border: 1px solid #3f3f46 !important;
  border-radius: 8px !important;
}
.recharts-tooltip-item {
  color: #fafafa !important;
  font-family: var(--font-mono), monospace !important;
  font-size: 12px !important;
}
.recharts-tooltip-label {
  color: #a1a1aa !important;
  font-family: var(--font-mono), monospace !important;
  font-size: 12px !important;
  margin-bottom: 4px !important;
}