Overview
This guide covers local development for both the Tinybird data project and the analytics dashboard. You’ll learn how to set up your environment, make changes, and test everything locally before deploying to production.
Prerequisites
Repository Structure
web-analytics-starter-kit/
├── tinybird/ # Tinybird data project
│ ├── src/
│ │ ├── client.ts # Tinybird client configuration
│ │ ├── datasources.ts # Data source definitions
│ │ ├── endpoints.ts # API endpoint definitions
│ │ └── ...
│ ├── tinybird.json # SDK configuration
│ └── package.json
├── dashboard/ # Next.js analytics dashboard
│ ├── app/
│ ├── components/
│ ├── lib/
│ └── package.json
└── middleware/ # Proxy server implementation
└── api/
└── tracking.js # Vercel edge function for proxying
Tinybird Data Project Setup
The Tinybird project contains all your data sources, pipes, and endpoints defined using the Tinybird SDK.
Initial Setup
- Clone the repository:
git clone https://github.com/tinybirdco/web-analytics-starter-kit
cd web-analytics-starter-kit/tinybird
- Install dependencies:
- Authenticate with Tinybird:
pnpm login
# or
npx tinybird login
This opens a browser window for authentication and creates a .tinybird file with your credentials.
- Configure environment variables:
Create a .env.local file:
TINYBIRD_TOKEN=p.your_admin_token_here
TINYBIRD_HOST=https://api.tinybird.co
For US East region:
TINYBIRD_HOST=https://api.us-east.tinybird.co
Development Workflow
Development Mode (Branch)
Start development mode to automatically sync changes to a Tinybird branch:
This command:
- Creates a development branch (if it doesn’t exist)
- Watches for file changes
- Automatically syncs changes to Tinybird
- Provides a live preview URL
Use branches for development to avoid affecting production data and endpoints.
Manual Build
Build and push changes to a branch:
Deploy to Production
Deploy changes to the main workspace:
This deploys to production. Make sure you’ve tested your changes in a branch first!
Preview Changes
Preview changes without deploying:
Project Configuration
The tinybird.json file configures the SDK:
{
"include": [
"src/client.ts",
"src/datasources.ts",
"src/endpoints.ts",
"src/materializations.ts",
"src/pipes.ts",
"src/tokens.ts",
"src/copies.ts",
"src/web-vitals.ts"
],
"token": "${TINYBIRD_TOKEN}",
"baseUrl": "${TINYBIRD_HOST}",
"devMode": "branch"
}
Files to include in the build
Admin token for authentication (from environment variable)
Tinybird API URL (from environment variable)
Development mode: branch creates isolated dev environments
Making Changes
Adding a New Endpoint
Create a new endpoint in src/endpoints.ts:
export const myCustomEndpoint = defineEndpoint("my_custom_endpoint", {
description: "My custom analytics endpoint",
tokens: [{ token: dashboardToken, scope: "READ" }],
nodes: [
node({
name: "query",
sql: `
SELECT
toDate(timestamp) as date,
count() as total_events
FROM analytics_events
WHERE timestamp >= {{ DateTime(date_from) }}
AND timestamp < {{ DateTime(date_to) }}
GROUP BY date
ORDER BY date DESC
`,
}),
],
params: {
date_from: p.dateTime().describe("Start date"),
date_to: p.dateTime().describe("End date"),
},
output: {
date: t.date(),
total_events: t.uint64(),
},
});
The changes will automatically sync if you’re running pnpm dev.
Modifying a Data Source
Edit src/datasources.ts to modify schema or engine settings:
export const analyticsEvents = defineDatasource("analytics_events", {
schema: {
timestamp: t.dateTime(),
// Add a new field
custom_field: t.string().default(""),
// ... other fields
},
engine: engine.mergeTree({
partitionKey: "toYYYYMM(timestamp)",
sortingKey: ["tenant_id", "domain", "timestamp"],
}),
});
Changing data source schemas requires careful migration. Test thoroughly in a branch.
Using Tinybird Local
For complete local development without cloud dependencies, use Tinybird Local:
# Start local Tinybird instance
tinybird local start
# Update environment to use local instance
export TINYBIRD_HOST=http://localhost:7181
# Deploy to local instance
pnpm deploy
Local instance runs on http://localhost:7181 and includes:
- Local ClickHouse database
- Tinybird API
- Admin UI
Dashboard Development
The dashboard is a Next.js application that visualizes your analytics data.
Setup
- Navigate to dashboard directory:
- Install dependencies:
- Configure environment:
Copy .env.example to .env or .env.local:
cp .env.example .env.local
Edit .env.local:
# Tinybird Configuration (server-only, required)
TINYBIRD_TOKEN=your-dashboard-token
TINYBIRD_HOST=https://api.tinybird.co
# Dashboard URL (public)
NEXT_PUBLIC_TINYBIRD_DASHBOARD_URL=http://localhost:3000
# Tracker token for analytics (public, optional)
NEXT_PUBLIC_TINYBIRD_TRACKER_TOKEN=
# Dashboard Basic Auth (server-only, defaults to admin/admin if not set)
DASHBOARD_USERNAME=admin
DASHBOARD_PASSWORD=changeme
# Optional: Set to "true" to disable auth during development
DISABLE_AUTH=true
# Optional: AI Chat
NEXT_PUBLIC_ASK_TINYBIRD_ENDPOINT="https://ask-tb.tinybird.live/api/chat"
Development Server
Start the development server:
The dashboard runs at http://localhost:3000
Authentication
The dashboard includes built-in authentication:
- Default credentials:
admin / admin
- Customize: Set
DASHBOARD_USERNAME and DASHBOARD_PASSWORD
- Disable for dev: Set
DISABLE_AUTH=true
Tech Stack
Project Structure
dashboard/
├── app/ # Next.js App Router
│ ├── api/ # API routes
│ │ ├── endpoints/ # Proxy to Tinybird endpoints
│ │ ├── auth/ # Authentication endpoints
│ │ └── config/ # Configuration endpoints
│ ├── page.tsx # Main dashboard page
│ └── layout.tsx # Root layout
├── components/ # React components
│ ├── charts/ # Chart components
│ ├── stats/ # Stat cards
│ └── ui/ # UI primitives
├── lib/ # Utilities
│ ├── api.ts # API client
│ ├── hooks/ # Custom React hooks
│ └── types/ # TypeScript types
└── public/ # Static assets
Making Changes
Adding a New Chart
Create a component in components/charts/:
// components/charts/CustomChart.tsx
import { useEndpoint } from '@/lib/hooks/use-endpoint';
import { LineChart, Line, XAxis, YAxis, Tooltip } from 'recharts';
export function CustomChart() {
const { data, error, isLoading } = useEndpoint('my_custom_endpoint', {
date_from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
date_to: new Date().toISOString(),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading data</div>;
return (
<LineChart width={600} height={300} data={data}>
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="total_events" stroke="#8884d8" />
</LineChart>
);
}
Using Custom Endpoints
The useEndpoint hook fetches data from your Tinybird endpoints:
import { useEndpoint } from '@/lib/hooks/use-endpoint';
const { data, error, isLoading } = useEndpoint('endpoint_name', {
param1: 'value1',
param2: 'value2',
});
Data is automatically cached and revalidated using SWR.
Using Tinybird Local with Dashboard
To develop against a local Tinybird instance:
# In .env.local
TINYBIRD_HOST=http://localhost:7181
Then start the dashboard:
Testing Locally
Test Tracking Script
Create a simple HTML page to test tracking:
<!DOCTYPE html>
<html>
<head>
<title>Analytics Test</title>
<script
defer
src="https://unpkg.com/@tinybirdco/flock.js"
data-token="YOUR_TRACKER_TOKEN"
data-host="http://localhost:7181"
></script>
</head>
<body>
<h1>Analytics Test Page</h1>
<button onclick="Tinybird.trackEvent('button_click', { button: 'test' })">
Track Event
</button>
</body>
</html>
Open in a browser and check your Tinybird workspace for events.
Query Data
Query your local or development branch:
# Using Tinybird CLI
tinybird sql "SELECT count() FROM analytics_events"
# Or via API
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://api.tinybird.co/v0/sql?q=SELECT%20count()%20FROM%20analytics_events"
Production Build
Build Dashboard
Test the production build locally:
Deploy Dashboard
Deploy to Vercel:
Or use the Vercel GitHub integration for automatic deployments.
Common Development Tasks
Add a Custom Event Type
- Track the event from your app:
Tinybird.trackEvent('signup_completed', {
plan: 'pro',
trial: false,
});
- Create an endpoint to query it:
export const signups = defineEndpoint("signups", {
nodes: [
node({
sql: `
SELECT
toDate(timestamp) as date,
JSONExtractString(payload, 'plan') as plan,
count() as signups
FROM analytics_events
WHERE action = 'signup_completed'
AND timestamp >= {{ DateTime(date_from) }}
GROUP BY date, plan
ORDER BY date DESC
`,
}),
],
// ... params and output
});
- Add a chart in the dashboard
Test Multi-Tenancy
Track events with tenant IDs:
<script
src="https://unpkg.com/@tinybirdco/flock.js"
data-token="YOUR_TRACKER_TOKEN"
data-tenant-id="test-tenant-1"
data-domain="test.local"
></script>
Query by tenant:
tinybird sql "SELECT count() FROM analytics_events WHERE tenant_id = 'test-tenant-1'"
Debug Authentication Issues
Enable authentication logs:
// middleware.ts
console.log('Auth check:', {
pathname: request.nextUrl.pathname,
hasAuth: !!request.headers.get('authorization'),
});
Troubleshooting
”Token not found” Error
Ensure your .env.local file has the correct token:
TINYBIRD_TOKEN=p.your_actual_token_here
“Connection refused” with Tinybird Local
Check if Tinybird Local is running:
tinybird local status
# If not running:
tinybird local start
Dashboard shows “No data”
Verify:
- Tracking script is sending events
- Dashboard token has correct permissions
- Date range includes data
- Check browser console for API errors
Changes not syncing
Restart dev mode:
# Stop (Ctrl+C)
# Start again
pnpm dev
Or manually push:
Best Practices
Use branches for development, deploy to production only after testing
Keep .env.local in .gitignore - never commit tokens
Use Tinybird Local for testing schema changes
Test with realistic data volumes before deploying
Document custom endpoints and their parameters