A modern, productivity-focused task management application built with Next.js 15 and Clean Architecture principles.
- Overview
- Tech Stack
- Prerequisites
- Project Structure
- Getting Started
- Development Workflow
- Architecture
- Authentication
- Scripts
- Troubleshooting
- Contributing
- Resources
Nowly is a "Today-first" task management web app designed to help users focus on what matters today. It features:
- Daily-focused task view - Prioritize tasks for today with morning/afternoon/evening sections
- Master task list - Comprehensive view of all tasks with advanced filtering and grouping
- Category management - Organize tasks with color-coded categories
- Recurring tasks - Automatically generate tasks based on custom recurrence patterns
- Flexible scheduling - Schedule tasks, set due dates, and manage priorities
- Responsive design - Beautiful, modern UI built with TailwindCSS and Shadcn/UI Production url: https://nowly-sand.vercel.app/
- Next.js 15 - React framework with App Router and Server Components
- React 19 - UI library
- TypeScript 5 - Type-safe JavaScript
- TailwindCSS 4 - Utility-first CSS framework
- Shadcn/UI - Accessible component library
- Lucide React - Icon library
- Supabase - Backend-as-a-Service (PostgreSQL + Auth + Realtime)
- React Query (TanStack Query) - Server state management (to be added)
- Zod - Schema validation (to be added)
- ESLint - Code linting
- Prettier - Code formatting
- Husky - Git hooks
- lint-staged - Run linters on staged files
- Clean Architecture - Domain-driven design with clear layer separation
- Repository Pattern - Abstracted data access layer
- Use Cases - Business logic isolated from frameworks
Before you begin, ensure you have the following installed:
-
Node.js (v20.x or higher)
node --version # Should be v20.x or higher -
npm (comes with Node.js)
npm --version # Should be 10.x or higher -
git --version
-
Supabase Account - You'll need a project set up
nowly/
├── app/ # Next.js App Router
│ ├── (public)/ # Public routes (landing, auth)
│ ├── (protected)/ # Authenticated routes (dashboard, tasks)
│ ├── actions/ # Server Actions
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Landing page
│ └── globals.css # Global styles
│
├── src/
│ ├── presentation/ # UI Layer (React components)
│ │ ├── components/ # Reusable UI components
│ │ ├── hooks/ # Custom React hooks
│ │ ├── pages/ # Page-level components
│ │ └── providers/ # Context providers
│ │
│ ├── application/ # Application Layer (Use Cases)
│ │ ├── tasks/ # Task-related business logic
│ │ └── categories/ # Category-related business logic
│ │
│ ├── domain/ # Domain Layer (Entities & Rules)
│ │ ├── model/ # Domain entities (Task, Category, etc.)
│ │ ├── types/ # Enums, value objects
│ │ └── validation/ # Zod schemas
│ │
│ ├── infrastructure/ # Infrastructure Layer (External Services)
│ │ ├── supabase/ # Supabase client & types
│ │ ├── repositories/ # Repository implementations
│ │ ├── services/ # External services
│ │ └── utils/ # Infrastructure utilities
│ │
│ ├── config/ # Configuration
│ │ ├── env.ts # Environment variables
│ │ └── constants.ts # Application constants
│ │
│ └── shared/ # Shared utilities
│ └── utils/ # Common utilities (cn, formatters, etc.)
│
├── tests/ # Tests (mirrors src/ structure)
│ ├── domain/
│ ├── application/
│ └── infrastructure/
│
├── public/ # Static assets
├── supabase/ # Supabase migrations (to be added)
├── .cursor/ # AI assistant rules & guidelines
└── [config files] # ESLint, TypeScript, Tailwind, etc.
┌─────────────────────────────────────────────────────┐
│ Presentation Layer (React Components, Hooks) │
│ • Client Components │
│ • Server Components │
│ • Server Actions │
└──────────────────────┬──────────────────────────────┘
│ uses
▼
┌─────────────────────────────────────────────────────┐
│ Application Layer (Use Cases) │
│ • Business Logic │
│ • Orchestration │
└──────────────────────┬──────────────────────────────┘
│ uses
▼
┌─────────────────────────────────────────────────────┐
│ Domain Layer (Entities, Types, Validation) │
│ • Pure TypeScript │
│ • No framework dependencies │
└─────────────────────────────────────────────────────┘
▲
│ implements
│
┌─────────────────────────────────────────────────────┐
│ Infrastructure Layer (Supabase, Repositories) │
│ • Database access │
│ • External services │
│ • Data transformation │
└─────────────────────────────────────────────────────┘
Dependency Rule: Dependencies only point inward. Outer layers depend on inner layers, never the reverse.
Follow these steps to set up the project on your local machine:
git clone https://github.com/yourusername/nowly.git
cd nowlynpm install-
Copy the example environment file:
cp .env.example .env.local
-
Get your Supabase credentials:
- Go to your Supabase Dashboard
- Select your project (or create a new one)
- Navigate to Settings > API
- Copy the following values:
Project URL→NEXT_PUBLIC_SUPABASE_URLanon public key→NEXT_PUBLIC_SUPABASE_ANON_KEYservice_role key→SUPABASE_SERVICE_ROLE_KEY(⚠️ keep secret!)
-
Edit
.env.localand fill in your values:# Public variables (safe for client-side) NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here NEXT_PUBLIC_APP_URL=http://localhost:3000 # Private variables (server-side only) SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here NODE_ENV=development
-
⚠️ Security Note:- Never commit
.env.local- It contains secrets! - Only use
NEXT_PUBLIC_*variables for non-sensitive data - Service role keys should never be exposed to the client
- Never commit
Option A: Use Supabase Dashboard (Recommended for now)
- Go to your Supabase Dashboard
- Navigate to SQL Editor
- Run the migration files from
supabase/migrations/(once created)
Option B: Local Supabase (Future)
# This will be available once local development is set up
npx supabase db pushnpm run devOpen http://localhost:3000 in your browser to see the app.
✅ You should see:
- The app running without errors
- No console errors about missing environment variables
- Able to access the landing page
# Development mode with hot reload
npm run dev
# Production build
npm run build
# Production server
npm run start# Lint your code
npm run lint
# Auto-fix linting issues
npm run lint:fix
# Format code with Prettier
npm run format
# Check formatting without modifying
npm run format:check-
On starting any planned changes: Create a feature branch: Create a feature branch:
git checkout -b feature/your-feature-name -
Make changes:
-
Before committing:
npm run format && npm run lint:fix -
Pre-commit hooks (Husky) will automatically:
- Format staged files with Prettier
- Lint staged files with ESLint
- Block commits if there are errors
-
Push branch
git push origin feature/your-feature-name -
Create Pull Request on GitHub
- Review changes in PR
- Review changes in Preview branch
- Merge to main
-
Writing code:
- Follow the Architecture Guidelines
- Use type-safe environment variables from
src/config/env.ts - Import constants from
src/config/constants.ts - Prefer Server Components over Client Components
- Transform database types at repository boundaries
Nowly uses Supabase database branching to provide isolated database environments for testing.
- Preview branches auto-create when you open a Pull Request
- Vercel preview deployments automatically connect to preview database
- Test in isolation - changes don't affect main database
- Auto-cleanup - preview branches delete when PR merges
Nowly follows Clean Architecture principles with strict layer separation:
| Layer | Purpose | Dependencies | Examples |
|---|---|---|---|
| Domain | Business entities and rules | None (pure TypeScript) | Task, Category, Zod schemas |
| Application | Business logic (use cases) | Domain only | createTask, updateTask |
| Infrastructure | External services, DB access | Domain, Application | Supabase, repositories |
| Presentation | UI components and hooks | Application, Domain | React components, hooks |
- Dependencies point inward - Outer layers depend on inner layers, never reverse
- Framework independence - Domain and Application layers have no framework dependencies
- Testability - Business logic is testable without UI or database
- Repository pattern - Data access is abstracted behind interfaces
- Type safety - Transform database types (snake_case) to domain types (camelCase) at boundaries
// 1. Domain Layer - Entity definition
// src/domain/model/Task.ts
export interface Task {
id: string;
userId: string;
title: string;
completed: boolean;
createdAt: Date;
updatedAt: Date;
}
// 2. Application Layer - Business logic
// src/application/tasks/createTask.usecase.ts
export async function createTask(
input: CreateTaskInput,
userId: string,
repository: TaskRepository
): Promise<Task> {
// Validation, business rules, orchestration
return await repository.create({ ...input, userId });
}
// 3. Infrastructure Layer - Data access
// src/infrastructure/repositories/SupabaseTaskRepository.ts
export class SupabaseTaskRepository implements TaskRepository {
async create(task: Omit<Task, 'id' | 'createdAt'>): Promise<Task> {
// Transform domain → database types
// Call Supabase
// Transform database → domain types
}
}
// 4. Presentation Layer - UI
// app/actions/createTaskAction.ts (Server Action)
('use server');
export async function createTaskAction(formData: FormData) {
const repository = new SupabaseTaskRepository(supabase);
return await createTask(data, userId, repository);
}📚 For detailed architecture guidelines, see:
Nowly uses Supabase Auth with server-side token exchange following Next.js 16 best practices.
┌─────────────────────────────────────────┐
│ User Action (Login/Signup/Reset) │
└──────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ React Form (Client Component) │
│ • Validates with Zod │
│ • Submits to Server Action │
└──────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Server Action (app/actions/) │
│ • Validates again on server │
│ • Calls Supabase server client │
│ • Returns result or redirects │
└──────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Auth Confirmation (for email links) │
│ • /auth/confirm route handler │
│ • Exchanges PKCE tokens server-side │
│ • Establishes session │
│ • Redirects to destination │
└──────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Proxy Middleware (proxy.ts) │
│ • Refreshes sessions on every request │
│ • Protects routes │
│ • Handles redirects │
└─────────────────────────────────────────┘
- User enters credentials in
/login - Form submits to
loginActionServer Action - Server validates and authenticates with Supabase
- On success: redirect to
/daily - On error: display error message
- User fills form at
/signup - Form submits to
signupActionServer Action - Server creates account and sends confirmation email
- User redirected to
/signup/success - User clicks email link →
/auth/confirm?token_hash=XXX&type=email&next=/daily - Auth handler verifies token and redirects to
/daily
- User requests reset at
/reset-password - Form submits to
resetPasswordRequestActionServer Action - Server sends reset email
- User clicks email link →
/auth/confirm?token_hash=XXX&type=recovery&next=/reset-password/confirm - Auth handler verifies token and redirects to reset form
- User enters new password
- Form submits to
resetPasswordConfirmActionServer Action - Password updated, user redirected to login
- ✅ Server-side token exchange - All auth tokens verified server-side
- ✅ PKCE flow - Prevents token interception attacks
- ✅ Server Actions - All form submissions go through secure Server Actions
- ✅ Session refresh - Middleware automatically refreshes expired sessions
- ✅ Protected routes - Middleware blocks unauthenticated access
- ✅ Email confirmation - Required before users can log in
- ✅ Rate limiting - Supabase provides built-in rate limiting
Required Supabase Setup:
-
Email Templates must be configured in Supabase Dashboard:
- Password Reset: Use
/auth/confirm?token_hash={{ .TokenHash }}&type=recovery&next=/reset-password/confirm - Email Confirmation: Use
/auth/confirm?token_hash={{ .TokenHash }}&type=email&next=/daily
- Password Reset: Use
-
Redirect URLs configured in Supabase:
- Local:
http://localhost:3000/auth/confirm - Production:
https://*.vercel.app/auth/confirm
- Local:
See detailed configuration: docs/AUTH_CONFIG.md
- Server Actions:
app/actions/*Action.ts - Auth Confirmation:
app/auth/confirm/route.ts - Proxy Middleware:
proxy.ts - Client Forms:
src/presentation/components/authentication/ - Validation Schemas:
src/domain/validation/auth.schema.ts
| Command | Description |
|---|---|
npm run dev |
Start development server (http://localhost:3000) |
npm run build |
Build for production |
npm run start |
Start production server |
npm run lint |
Lint code with ESLint |
npm run lint:fix |
Auto-fix linting issues |
npm run format |
Format code with Prettier |
npm run format:check |
Check formatting without modifying |
Problem: App fails to start with environment variable error.
Solution:
# Ensure .env.local exists
ls -la .env.local
# If missing, copy from example
cp .env.example .env.local
# Edit and fill in your Supabase credentialsProblem: Changed .env.local but values aren't reflected.
Solution:
# Stop the dev server (Ctrl+C)
# Clear Next.js cache
rm -rf .next
# Restart dev server
npm run devProblem: Security error when trying to use private variable in Client Component.
Solution:
- Private variables (without
NEXT_PUBLIC_prefix) can only be used in:- Server Components
- Server Actions
- API Routes
- Never use them in Client Components (files with
'use client')
Problem: Git commit is blocked by linting/formatting errors.
Solution:
# Fix linting and formatting issues
npm run format && npm run lint:fix
# Try committing again
git commit -m "your message"Problem: Another service is using port 3000.
Solution:
# Kill the process using port 3000 (macOS/Linux)
lsof -ti:3000 | xargs kill -9
# Or use a different port
npm run dev -- -p 3001Problem: Missing dependencies after pulling from git.
Solution:
# Clean install
rm -rf node_modules package-lock.json
npm installProblem: VS Code or Cursor shows TypeScript errors.
Solution:
# Restart TypeScript server in your editor
# In VS Code: Cmd+Shift+P → "TypeScript: Restart TS Server"
# Or rebuild
npm run build-
Read the architecture guidelines in
.cursor/rules/before coding -
Follow the coding standards:
- Use TypeScript strictly (no
anytypes) - Follow Clean Architecture principles
- Write self-documenting code with clear naming
- Add JSDoc comments for complex logic
- Use TypeScript strictly (no
-
Before committing:
# Format and lint npm run format && npm run lint:fix # Ensure it builds npm run build
-
Commit message format:
feat: add task filtering by category fix: resolve date formatting issue perf: optimize task list rendering docs: update setup instructions -
Pull request process:
- Create a feature branch from
main - Keep PRs focused and small
- Include screenshots for UI changes
- Update documentation if needed
- Ensure all checks pass
- Create a feature branch from
- Use npm only - No yarn, pnpm, or bun
- Absolute imports - Use
@/src/...or@/app/... - File naming:
- Components:
PascalCase.tsx - Use cases:
camelCase.usecase.ts - Repositories:
SupabasePascalCase.ts - Hooks:
useSomething.ts
- Components:
- Folder naming:
kebab-case
-
Project Guidelines:
-
Module Documentation:
- Next.js Documentation
- React Documentation
- TypeScript Documentation
- Supabase Documentation
- TailwindCSS Documentation
- Shadcn/UI Documentation
- Zod Documentation
- TanStack Query Documentation
- GitHub Issues - Bug reports and feature requests
- GitHub Discussions - Questions and community chat
This project is private and proprietary.
Thank you for contributing to Nowly! If you have any questions, don't hesitate to:
- Check the documentation in
.cursor/rules/ - Open a GitHub Discussion
- Ask in your team chat
Happy coding! 🦉