Skip to content

Install Dashboard Stack

A complete, beginner-friendly guide to install and configure the modern dashboard tech stack. This guide will take you from zero to a fully functional dashboard with React 19, Cloudflare Workers, Neon PostgreSQL, and more.


Before starting, ensure you have:

Computer

macOS, Windows, or Linux with admin access

Internet

Stable internet connection for downloads

Accounts

Free accounts: GitHub, Cloudflare, Neon (we’ll create these)

Time

~45-60 minutes to complete full setup


Bun is our primary build tool and package manager - it’s 4x faster than npm!

  1. Install Bun

    Terminal window
    curl -fsSL https://bun.sh/install | bash
  2. Verify Installation

    Terminal window
    bun --version

    You should see something like: 1.0.0 or higher

  3. Restart Your Terminal Close and reopen your terminal to ensure bun is in your PATH

Git is required for version control and some tools we’ll use.

Terminal window
# Using Homebrew (recommended)
brew install git
# Or use Xcode Command Line Tools
xcode-select --install

Verify Git:

Terminal window
git --version

While you can use any editor, VS Code has the best support for our stack.

  1. Download from code.visualstudio.com
  2. Install the application
  3. Open VS Code

Install Recommended Extensions:

Terminal window
# Open VS Code and install these extensions
code --install-extension dbaeumer.vscode-eslint
code --install-extension esbenp.prettier-vscode
code --install-extension bradlc.vscode-tailwindcss
code --install-extension astro-build.astro-vscode

  1. Visit github.com/signup
  2. Create a free account
  3. Verify your email address
  4. Complete the profile setup
  1. Visit dash.cloudflare.com/sign-up
  2. Sign up with your email
  3. Verify your email
  4. Choose the Free plan (perfect for development)
  1. Visit neon.tech
  2. Click “Sign Up” and use GitHub to sign in (fastest)
  3. Create your first project:
    • Project name: my-dashboard-db
    • Region: Choose closest to you
    • PostgreSQL version: 16 (default)
  4. Save your connection string - you’ll need it later!

  1. Open Terminal in your projects folder

  2. Create React Project with Vite

    Terminal window
    bun create vite my-dashboard --template react-ts
  3. Navigate to Project

    Terminal window
    cd my-dashboard
  4. Install Dependencies

    Terminal window
    bun install
  5. Test Development Server

    Terminal window
    bun run dev

    Visit http://localhost:5173 - you should see the Vite + React page!

  6. Stop the server (Press Ctrl+C)

  • Directorymy-dashboard/
    • Directorysrc/
      • App.tsx
      • main.tsx
      • index.css
    • Directorypublic/
    • package.json
    • tsconfig.json
    • vite.config.ts
    • index.html

Terminal window
# Routing
bun add @tanstack/react-router
bun add -D @tanstack/router-vite-plugin
# Data Fetching & State
bun add @tanstack/react-query
bun add zustand
# Forms & Validation
bun add react-hook-form zod @hookform/resolvers
Terminal window
# Tailwind CSS
bun add -D tailwindcss postcss autoprefixer
bunx tailwindcss init -p
# shadcn/ui dependencies
bun add tailwindcss-animate class-variance-authority clsx tailwind-merge
bun add lucide-react
Terminal window
# Charts & Tables
bun add recharts @tremor/react
bun add @tanstack/react-table

  1. Update tailwind.config.js

    tailwind.config.js
    /** @type {import('tailwindcss').Config} */
    export default {
    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))",
    },
    secondary: {
    DEFAULT: "hsl(var(--secondary))",
    foreground: "hsl(var(--secondary-foreground))",
    },
    },
    borderRadius: {
    lg: "var(--radius)",
    md: "calc(var(--radius) - 2px)",
    sm: "calc(var(--radius) - 4px)",
    },
    },
    },
    plugins: [require("tailwindcss-animate")],
    }
  2. Update src/index.css

    src/index.css
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    @layer base {
    :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --ring: 222.2 84% 4.9%;
    --radius: 0.5rem;
    }
    .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --ring: 212.7 26.8% 83.9%;
    }
    }

  1. Initialize shadcn/ui

    Terminal window
    bunx shadcn-ui@latest init

    Answer the prompts:

    • Style: Default
    • Base color: Slate
    • CSS variables: Yes
  2. Add Essential Components

    Terminal window
    bunx shadcn-ui@latest add button
    bunx shadcn-ui@latest add card
    bunx shadcn-ui@latest add input
    bunx shadcn-ui@latest add label
    bunx shadcn-ui@latest add table
    bunx shadcn-ui@latest add dialog
    bunx shadcn-ui@latest add dropdown-menu
  • Directorymy-dashboard/
    • Directorysrc/
      • Directorycomponents/
        • Directoryui/
          • button.tsx
          • card.tsx
          • input.tsx
          • label.tsx
          • table.tsx
      • Directorylib/
        • utils.ts
      • App.tsx

Terminal window
# Drizzle ORM
bun add drizzle-orm @neondatabase/serverless
bun add -D drizzle-kit
  1. Create drizzle.config.ts in project root

    drizzle.config.ts
    import type { Config } from 'drizzle-kit';
    export default {
    schema: './src/db/schema.ts',
    out: './drizzle',
    driver: 'pg',
    dbCredentials: {
    connectionString: process.env.DATABASE_URL!,
    },
    } satisfies Config;
  2. Create .env file in project root

    .env
    DATABASE_URL=postgresql://user:password@ep-xyz.region.aws.neon.tech/dbname
  3. Add .env to .gitignore

    Terminal window
    echo ".env" >> .gitignore
  1. Create folder structure

    Terminal window
    mkdir -p src/db
  2. Create src/db/schema.ts

    src/db/schema.ts
    import { pgTable, text, serial, timestamp } from 'drizzle-orm/pg-core';
    export const users = pgTable('users', {
    id: serial('id').primaryKey(),
    name: text('name').notNull(),
    email: text('email').notNull().unique(),
    createdAt: timestamp('created_at').defaultNow(),
    });
    export const posts = pgTable('posts', {
    id: serial('id').primaryKey(),
    title: text('title').notNull(),
    content: text('content'),
    userId: serial('user_id').references(() => users.id),
    createdAt: timestamp('created_at').defaultNow(),
    });
  3. Generate Migration

    Terminal window
    bunx drizzle-kit generate:pg
  4. Push to Database

    Terminal window
    bunx drizzle-kit push:pg

Create src/db/client.ts:

src/db/client.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(import.meta.env.VITE_DATABASE_URL!);
export const db = drizzle(sql, { schema });
  • Directorymy-dashboard/
    • Directorysrc/
      • Directorydb/
        • schema.ts
        • client.ts
    • Directorydrizzle/
      • 0000_initial.sql
    • drizzle.config.ts
    • .env

Part 8: Setup API Backend (Hono + Cloudflare Workers)

Section titled “Part 8: Setup API Backend (Hono + Cloudflare Workers)”
Terminal window
# Create API folder
mkdir api
cd api
# Initialize Hono project
bun create hono my-api
cd my-api
# Install dependencies
bun install
Terminal window
# Add Better Auth for authentication
bun add better-auth
# Add database support
bun add drizzle-orm @neondatabase/serverless
# Add Cloudflare Workers support
bun add -D wrangler

Create src/index.ts:

api/my-api/src/index.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
const app = new Hono()
// Enable CORS for frontend
app.use('/*', cors())
// Health check
app.get('/', (c) => {
return c.json({ message: 'Dashboard API' })
})
// Users endpoint
app.get('/api/users', async (c) => {
// Database query will go here
return c.json({ users: [] })
})
// Posts endpoint
app.get('/api/posts', async (c) => {
return c.json({ posts: [] })
})
export default app

Create wrangler.toml:

api/my-api/wrangler.toml
name = "my-dashboard-api"
main = "src/index.ts"
compatibility_date = "2025-01-13"
[vars]
ENVIRONMENT = "production"
Terminal window
bun run dev

Visit http://localhost:8787 - you should see {"message": "Dashboard API"}!

  • Directorymy-dashboard/
    • Directoryapi/
      • Directorymy-api/
        • Directorysrc/
          • index.ts
        • wrangler.toml
        • package.json
    • Directorysrc/

Create src/lib/query-client.ts:

src/lib/query-client.ts
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
retry: 1,
},
},
});

Update src/main.tsx:

src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { QueryClientProvider } from '@tanstack/react-query'
import { queryClient } from './lib/query-client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
)

Create src/lib/api.ts:

src/lib/api.ts
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8787';
export async function fetchUsers() {
const res = await fetch(`${API_URL}/api/users`);
if (!res.ok) throw new Error('Failed to fetch users');
return res.json();
}
export async function fetchPosts() {
const res = await fetch(`${API_URL}/api/posts`);
if (!res.ok) throw new Error('Failed to fetch posts');
return res.json();
}

Create src/components/Dashboard.tsx:

src/components/Dashboard.tsx
import { useQuery } from '@tanstack/react-query';
import { fetchUsers } from '../lib/api';
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
export function Dashboard() {
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
if (isLoading) return <div>Loading...</div>;
return (
<div className="p-8">
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<Card>
<CardHeader>
<CardTitle>Total Users</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">{data?.users?.length || 0}</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Active Projects</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">0</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Revenue</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">$0</p>
</CardContent>
</Card>
</div>
</div>
);
}
src/App.tsx
import { Dashboard } from './components/Dashboard'
function App() {
return <Dashboard />
}
export default App

Terminal window
# In your API project
cd api/my-api
bun add better-auth

Create src/lib/auth.ts:

api/my-api/src/lib/auth.ts
import { betterAuth } from 'better-auth';
export const auth = betterAuth({
database: {
provider: 'pg',
url: process.env.DATABASE_URL!,
},
emailAndPassword: {
enabled: true,
},
});

Update src/index.ts:

api/my-api/src/index.ts
import { Hono } from 'hono'
import { auth } from './lib/auth'
const app = new Hono()
// Mount auth routes
app.use('/api/auth/*', async (c) => {
return auth.handler(c.req.raw)
})
// ... rest of your routes

  1. Login to Cloudflare

    Terminal window
    cd api/my-api
    bunx wrangler login
  2. Add Environment Variables

    Terminal window
    bunx wrangler secret put DATABASE_URL
    # Paste your Neon connection string
  3. Deploy

    Terminal window
    bunx wrangler deploy
  4. Save Your API URL Copy the URL from the output: https://my-dashboard-api.YOUR-SUBDOMAIN.workers.dev

Step 2: Deploy Frontend to Cloudflare Pages

Section titled “Step 2: Deploy Frontend to Cloudflare Pages”
  1. Build Your Frontend

    Terminal window
    cd my-dashboard
    bun run build
  2. Install Wrangler

    Terminal window
    bun add -D wrangler
  3. Deploy to Pages

    Terminal window
    bunx wrangler pages publish dist
  4. Your Dashboard is Live! You’ll get a URL like: https://my-dashboard.pages.dev


  1. Sign up at clickhouse.com/cloud

  2. Install ClickHouse Client

    Terminal window
    bun add @clickhouse/client
  3. Create Analytics Schema

    CREATE TABLE analytics.events (
    event_name String,
    user_id UInt64,
    timestamp DateTime,
    properties String
    ) ENGINE = MergeTree()
    ORDER BY (timestamp, event_name);
  4. Track Events in Your App

    import { createClient } from '@clickhouse/client';
    const client = createClient({
    host: process.env.CLICKHOUSE_HOST,
    password: process.env.CLICKHOUSE_PASSWORD,
    });
    await client.insert({
    table: 'analytics.events',
    values: [{
    event_name: 'page_view',
    user_id: 123,
    timestamp: new Date(),
    properties: JSON.stringify({ page: '/dashboard' })
    }]
    });
  1. Sign up at sentry.io

  2. Install Sentry

    Terminal window
    bun add @sentry/react
  3. Initialize Sentry

    src/main.tsx
    import * as Sentry from "@sentry/react";
    Sentry.init({
    dsn: "YOUR_SENTRY_DSN",
    environment: "production",
    });

Make sure everything works:

  1. Frontend Development Server

    Terminal window
    cd my-dashboard
    bun run dev

    ✅ Opens at http://localhost:5173

  2. API Development Server

    Terminal window
    cd api/my-api
    bun run dev

    ✅ Opens at http://localhost:8787

  3. Database Connection ✅ Can query Neon database ✅ Tables exist in Neon dashboard

  4. UI Components ✅ shadcn/ui components render correctly ✅ Tailwind CSS styles applied ✅ Dark mode works

  5. Production Deployment ✅ API deployed to Cloudflare Workers ✅ Frontend deployed to Cloudflare Pages ✅ Environment variables configured


  • Directorymy-dashboard/
    • Directoryapi/
      • Directorymy-api/
        • Directorysrc/
          • Directorylib/
            • auth.ts
          • index.ts
        • wrangler.toml
        • package.json
    • Directorysrc/
      • Directorycomponents/
        • Directoryui/
          • button.tsx
          • card.tsx
          • input.tsx
        • Dashboard.tsx
      • Directorydb/
        • schema.ts
        • client.ts
      • Directorylib/
        • api.ts
        • query-client.ts
        • utils.ts
      • App.tsx
      • main.tsx
      • index.css
    • Directorydrizzle/
      • 0000_initial.sql
    • Directorypublic/
    • .env
    • drizzle.config.ts
    • tailwind.config.js
    • tsconfig.json
    • vite.config.ts
    • package.json


Now that your dashboard is set up, you can:


You’ve successfully installed and configured:

Frontend: React 19 + TypeScript + Tailwind CSS + shadcn/ui ✅ Backend: Hono API on Cloudflare Workers ✅ Database: Neon PostgreSQL with Drizzle ORM ✅ State Management: TanStack Query + Zustand ✅ Authentication: Better Auth ✅ Deployment: Cloudflare Workers + Pages

Your dashboard is production-ready! 🚀


Last updated: October 2025