A production-ready Next.js 14 dashboard for tracking Kanban team productivity using Jira Cloud API. Calculates throughput, cycle time metrics (average and p85), and breakdowns by assignee and issue type.
- 📊 Real-time Metrics: Throughput, average cycle time, and p85 cycle time
- 📈 Historical Trends: View last 4 quarters of performance data
- 👥 Team Breakdowns: See top assignees and issue type distributions
- 🔄 Quarter Navigation: Easily switch between quarters
- 📥 CSV Export: Download raw metrics for external analysis
- ⚡ Optimized Performance: Server-side rendering with 1-hour caching
- 📱 Responsive Design: Works on desktop, tablet, and mobile
┌─────────────────────────────────────────┐
│ Next.js 14 App Router (TypeScript) │
├─────────────────────────────────────────┤
│ app/ - Server components│
│ components/ - Client & UI │
│ lib/ - Utilities │
│ ├── env.ts - Env validation │
│ ├── jira.ts - Jira API client │
│ ├── types.ts - TypeScript types │
│ └── utils.ts - Helpers │
├─────────────────────────────────────────┤
│ Jira Cloud API │
│ (Basic Auth, 1-hour cache) │
└─────────────────────────────────────────┘
- Framework: Next.js 14 with App Router
- Language: TypeScript (strict mode)
- Styling: Tailwind CSS
- Charts: Recharts
- Dates: date-fns
- UI Components: shadcn/ui + custom components
- Deployment: Vercel
- Node.js 18+
- pnpm 8+
- Jira Cloud instance with API token
git clone <repo>
cd jira-flow-metrics
pnpm installCopy .env.example to .env.local:
cp .env.example .env.localFill in your Jira details:
JIRA_DOMAIN=your-domain.atlassian.net
JIRA_EMAIL=your-email@example.com
JIRA_TOKEN=your-jira-api-token
JIRA_PROJECT=YOUR_PROJECT_KEY
JIRA_DONE_STATUS=Done
JIRA_IN_PROGRESS_STATUS=In Progress- Go to Jira API Token Page
- Click "Create API token"
- Copy the token and paste it in
.env.local
- JIRA_DOMAIN: From your Jira URL (e.g.,
company.atlassian.net) - JIRA_PROJECT: Your project key (e.g.,
PROJfrom PROJ-123) - JIRA_DONE_STATUS: Name of your "done" workflow status (e.g.,
Done,Closed) - JIRA_IN_PROGRESS_STATUS: Name of your "in progress" status (e.g.,
In Progress,Started)
pnpm devOpen http://localhost:3000 and you'll be redirected to /dashboard.
| Variable | Required | Description | Example |
|---|---|---|---|
JIRA_DOMAIN |
Yes | Jira Cloud domain | company.atlassian.net |
JIRA_EMAIL |
Yes | Email for API auth | user@company.com |
JIRA_TOKEN |
Yes | Jira API token | (from API token page) |
JIRA_PROJECT |
Yes | Project key | MYPROJ |
JIRA_DONE_STATUS |
Yes | Status name for completed issues | Done |
JIRA_IN_PROGRESS_STATUS |
Yes | Status name for in-progress issues | In Progress |
All variables are validated on app startup. Missing variables will throw a descriptive error.
Number of issues completed in the quarter. Shows team velocity and capacity.
Mean time from issue creation to resolution in days. Lower is better. Useful for understanding typical turnaround time.
85th percentile of cycle times. Shows the "upper bound" time for most issues. Important for SLA planning—50% of issues complete faster than this, 15% take longer.
Count of completed issues per team member. Identifies high-performers and workload distribution.
Count of completed issues grouped by type (Story, Bug, Task, etc.). Highlights what the team is working on.
The main view displays:
- Header: Current quarter and metadata
- Controls: Quarter selector dropdown, CSV export button
- KPI Cards: Throughput, Avg Cycle Time, P85 Cycle Time with QoQ deltas
- Charts:
- Throughput trend (last 4 quarters)
- Cycle time trend (avg + p85 lines)
- Top assignees (current quarter)
- Issues by type (current quarter)
- Raw Metrics Table: Quarterly summary table
Click the quarter selector to view historical data. URL updates to ?quarter=2026Q3 and data refetches server-side.
Click "Export CSV" to download raw metrics for the selected quarter with columns:
- Key, Summary, Type, Assignee, Created, Resolved, Cycle_Days
- 401 Unauthorized: Invalid credentials (check email/token)
- 400 Bad Request: Invalid project key or JQL (check JIRA_PROJECT and status names)
- Network Error: Connection failed (check domain URL)
- Missing Env Vars: App will not start without all required variables
- Caching: All Jira API responses cached for 1 hour using Next.js
revalidate: 3600 - Parallel Fetching: Last 4 quarters fetched in parallel with
Promise.all - Server Rendering: All metrics calculated server-side; charts render client-side
- Optimized Bundles: Tailwind and Recharts tree-shaken automatically
git add .
git commit -m "Initial commit"
git push origin main- Go to Vercel Dashboard
- Import your GitHub repository
- Add environment variables (from
.env.local) - Click "Deploy"
In Vercel Dashboard → Project Settings → Environment Variables, add:
JIRA_DOMAIN=...
JIRA_EMAIL=...
JIRA_TOKEN=...
JIRA_PROJECT=...
JIRA_DONE_STATUS=...
JIRA_IN_PROGRESS_STATUS=...
Your app is live and will auto-redeploy on main branch pushes.
jira-flow-metrics/
├── app/
│ ├── layout.tsx # Root layout with env validation
│ ├── page.tsx # Redirect to /dashboard
│ ├── globals.css # Tailwind + global styles
│ ├── dashboard/
│ │ ├── page.tsx # Main dashboard (SSR)
│ │ ├── loading.tsx # Loading skeleton
│ │ └── error.tsx # Error boundary
│ └── api/
│ └── export/
│ └── route.ts # CSV export endpoint
├── components/
│ ├── ui/ # shadcn/ui base components
│ │ ├── card.tsx
│ │ ├── table.tsx
│ │ └── button.tsx
│ ├── kpi-card.tsx # KPI metric card
│ ├── charts.tsx # Recharts components
│ ├── quarter-selector.tsx # Quarter dropdown
│ └── raw-metrics-table.tsx # Metrics summary table
├── lib/
│ ├── env.ts # Environment validation
│ ├── jira.ts # Jira API client & calculations
│ ├── types.ts # TypeScript interfaces
│ └── utils.ts # Utility functions
├── package.json
├── tsconfig.json
├── tailwind.config.ts
├── postcss.config.js
├── next.config.js
├── .env.example
├── .gitignore
└── README.md
getQuarterData(quarter: string): Fetch and calculate metrics for a single quartergetMultipleQuartersData(quarters: string[]): Parallel fetch for multiple quartersgetQuarterDates(quarter: string): Parse quarter string to date rangepercentile(arr: number[], p: number): Calculate p-th percentilegetCurrentQuarter(): Get current quarter in "YYYYQX" format
- Accepts
?quarter=2026Q3URL param - Fetches last 4 quarters in parallel
- Calculates QoQ deltas for KPI cards
- Passes metrics to chart components
To add a new metric (e.g., escaped issues):
- Add field to
QuarterMetricsinlib/types.ts - Implement calculation in
getQuarterData()inlib/jira.ts - Create new chart component or KPI card in
components/ - Add to dashboard layout in
app/dashboard/page.tsx
Fix: Ensure all variables in .env.example are filled in .env.local.
cat .env.example # Check required vars
cat .env.local # Verify they're set
npm run dev # Should work nowFix: Verify credentials and project key:
- Test domain:
curl https://your-domain.atlassian.net/rest/api/3/myself -u email:token - Verify status names: Go to Project Settings → Workflows → check exact status names
- Check logs:
npm run devoutput shows Jira errors
Fix: Open browser DevTools → Console. Look for Recharts errors. Ensure data is being fetched (check Network tab for /dashboard?quarter=...).
This is normal. P85 is the 85th percentile—by definition, 85% of issues have cycle times below this value. It's always >= average.
This is a template for your team's use. To extend:
- Create feature branches:
git checkout -b feat/new-metric - Test locally:
npm run dev - Build for production:
npm run build && npm run start - Commit and push
- Deploy to Vercel
- Reduce cache time if metrics need real-time updates: Change
revalidate: 3600inlib/jira.ts - Filter by assignee: Add UI filter dropdown using
getQuarterData()results - Add WIP chart: Track in-progress issues with similar Jira JQL
- Pagination: For teams >200 issues/quarter, add pagination in
makeJiraRequest()
MIT
For issues or questions:
- Check Jira API docs: https://developer.atlassian.com/cloud/jira/rest/v3/
- Check Next.js docs: https://nextjs.org/docs
- Check error boundary:
/dashboarderror component shows detailed messages
Built with Next.js, Tailwind CSS, and Recharts. Deployed to Vercel.