Skip to main content

Overview

This page covers advanced configuration options for customizing Quail BI beyond basic environment variables.

Next.js Configuration

Quail’s Next.js configuration is defined in next.config.mjs:
next.config.mjs
import { setupDevPlatform } from '@cloudflare/next-on-pages/next-dev';

if (process.env.NODE_ENV === 'development') {
  await setupDevPlatform();
}

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
  reactStrictMode: true,
  images: {
    domains: ['media.brand.dev'],
  },
  experimental: {
    optimizePackageImports: ['recharts', '@radix-ui/react-icons'],
  },
};

export default nextConfig;

Output Mode

output: 'standalone'
The standalone output mode creates a minimal Node.js server with all required dependencies. This is ideal for containerized deployments.

Image Domains

images: {
  domains: ['media.brand.dev'],
}
Add domains that host images you want to use with Next.js <Image> component:
images: {
  domains: [
    'media.brand.dev',
    'your-cdn.com',
    'avatars.githubusercontent.com',
  ],
}

Package Import Optimization

experimental: {
  optimizePackageImports: ['recharts', '@radix-ui/react-icons'],
}
This reduces bundle size by only importing used components from large libraries.

TypeScript Configuration

TypeScript settings are in tsconfig.json:
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Key Settings

"strict": true
Enables all strict type-checking options. Recommended for catching bugs early.
"paths": {
  "@/*": ["./*"]
}
Allows importing with @/ prefix:
import { Button } from '@/components/ui/button';
instead of:
import { Button } from '../../components/ui/button';

Middleware Configuration

The middleware.ts file handles authentication and request processing:
middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value;
        },
        set(name: string, value: string, options: any) {
          response.cookies.set({
            name,
            value,
            ...options,
          });
        },
        remove(name: string, options: any) {
          response.cookies.set({
            name,
            value: '',
            ...options,
          });
        },
      },
    }
  );

  const { data: { session } } = await supabase.auth.getSession();

  // Protect authenticated routes
  if (!session && request.nextUrl.pathname.startsWith('/app')) {
    const redirectUrl = new URL('/login', request.url);
    redirectUrl.searchParams.set('redirect', request.nextUrl.pathname);
    return NextResponse.redirect(redirectUrl);
  }

  return response;
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};

Customizing Protected Routes

Add or remove protected path patterns:
// Protect all /app and /dashboard routes
if (!session && (
  request.nextUrl.pathname.startsWith('/app') ||
  request.nextUrl.pathname.startsWith('/dashboard')
)) {
  // Redirect to login
}

Customizing Matcher

The matcher determines which routes run middleware:
export const config = {
  matcher: [
    // Run on all routes except:
    '/((?!_next/static|_next/image|favicon.ico|api/public).*)',
  ],
};

Tailwind Configuration

Tailwind CSS is configured in tailwind.config.ts:
tailwind.config.ts
import type { Config } from 'tailwindcss';
import tailwindcssAnimate from 'tailwindcss-animate';

const config: Config = {
  darkMode: ['class'],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
  ],
  theme: {
    extend: {
      colors: {
        border: 'hsl(var(--border))',
        input: 'hsl(var(--input))',
        ring: 'hsl(var(--ring))',
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))',
        },
        // ... other colors
      },
      borderRadius: {
        lg: 'var(--radius)',
        md: 'calc(var(--radius) - 2px)',
        sm: 'calc(var(--radius) - 4px)',
      },
    },
  },
  plugins: [tailwindcssAnimate],
};

export default config;

Custom Theme Colors

Modify colors in app/globals.css:
app/globals.css
@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    /* ... other variables */
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --primary: 210 40% 98%;
    /* ... other variables */
  }
}

MongoDB Configuration

MongoDB client configuration in lib/config/mongodb.ts:
lib/config/mongodb.ts
import { MongoClient } from 'mongodb';

if (!process.env.MONGODB_URI) {
  throw new Error('Please add your MongoDB URI to .env.local');
}

const uri = process.env.MONGODB_URI;
const options = {
  maxPoolSize: 10,
  minPoolSize: 5,
  maxIdleTimeMS: 30000,
};

let client: MongoClient;
let clientPromise: Promise<MongoClient>;

if (process.env.NODE_ENV === 'development') {
  // In development, use a global variable to preserve the client across hot reloads
  let globalWithMongo = global as typeof globalThis & {
    _mongoClientPromise?: Promise<MongoClient>;
  };

  if (!globalWithMongo._mongoClientPromise) {
    client = new MongoClient(uri, options);
    globalWithMongo._mongoClientPromise = client.connect();
  }
  clientPromise = globalWithMongo._mongoClientPromise;
} else {
  // In production, create a new client
  client = new MongoClient(uri, options);
  clientPromise = client.connect();
}

export default clientPromise;

Connection Pool Settings

const options = {
  maxPoolSize: 10,    // Maximum connections
  minPoolSize: 5,     // Minimum connections
  maxIdleTimeMS: 30000, // Close idle connections after 30s
};
Adjust pool sizes based on your traffic:
  • Low traffic: maxPoolSize: 5, minPoolSize: 2
  • High traffic: maxPoolSize: 50, minPoolSize: 10

AI Provider Configuration

Quail configures Azure AI inline in each API route:
app/app/api/biChat/route.ts
import { createAzure } from '@ai-sdk/azure';

// Azure OpenAI configuration
const azure = createAzure({
  resourceName: process.env.NEXT_PUBLIC_AZURE_RESOURCE_NAME!,
  apiKey: process.env.NEXT_PUBLIC_AZURE_API_KEY!,
});

// Model selection is dynamic based on request parameters

Switching AI Providers

import { openai } from '@ai-sdk/openai';

export const model = openai('gpt-4o');
Set OPENAI_API_KEY in environment.

Component Library Configuration

Components are configured in components.json:
components.json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "slate",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils"
  }
}
This configuration is used by the shadcn/ui CLI for adding components.

Build Configuration

Customize the build process in package.json:
package.json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",
    "start": "next start",
    "lint": "next lint"
  }
}

Turbopack (Development)

"dev": "next dev --turbopack"
Turbopack is Next.js’s new bundler, providing faster hot module replacement. Remove --turbopack to use webpack.

Standalone Build

"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/"
Creates a self-contained build in .next/standalone/ with:
  • Minimal Node.js server
  • All required dependencies
  • Static assets
  • Public files

Performance Tuning

Enable Caching

Add Redis for caching query results:
lib/cache/redis.ts
import { Redis } from '@upstash/redis';

const redis = new Redis({
  url: process.env.REDIS_URL!,
  token: process.env.REDIS_TOKEN!,
});

export async function cacheQuery(
  key: string,
  data: any,
  ttl: number = 300 // 5 minutes
) {
  await redis.set(key, JSON.stringify(data), { ex: ttl });
}

export async function getCachedQuery(key: string) {
  const data = await redis.get(key);
  return data ? JSON.parse(data) : null;
}

Enable Compression

Add compression middleware:
middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  
  // Enable compression
  response.headers.set('Content-Encoding', 'gzip');
  
  return response;
}

Database Indexing

Add indexes to MongoDB collections:
// In MongoDB shell or via driver
db.connections.createIndex({ userId: 1 });
db.dashboards.createIndex({ userId: 1, createdAt: -1 });
db.charts.createIndex({ dashboardId: 1 });

Environment Variables

Configure all environment variables

Security

Security configuration and best practices

AI Integration

Configure AI providers and features

Troubleshooting

Fix configuration issues

Build docs developers (and LLMs) love