Loading post...
Loading post...

Job hunting is exhausting. Between crafting tailored resumes, tracking multiple applications, and remembering which stage each one is in, it's easy to lose track of everything. I experienced this firsthand during my own job search, and like any developer facing a problem, I decided to build a solution.
That's how JobTrack was born—an open-source job application tracking system designed to bring clarity and structure to the chaos of job searching.
During my job search, I tried various approaches to stay organized:
What I needed was something that combined the structure of a database with the visual clarity of a kanban board, specific to the job application journey. More importantly, I wanted complete control over my data and the ability to customize the tool to my exact needs.
JobTrack is a full-stack application that treats job applications as a pipeline. Every application moves through stages—from initial submission to final decision—and the interface reflects this journey visually.
Kanban-Style Tracking: Applications move through 8 stages (Applied → Test Case → HR Interview → Technical Interview → Management Interview → Offer → Accepted/Rejected). Drag and drop makes status updates effortless.
Complete Application Context: Each application stores everything relevant—company name, position, work type, location, salary expectations, application source, recruiter contacts, and most importantly, the actual resume you submitted (as a PDF).
Smart Filtering: Real-time search across companies and positions, with multi-filter support for status, work type, and source. All filters persist in the URL, so you can bookmark or share specific views.
Pin Critical Applications: Keep the most important applications at the top of your list, regardless of other sorting or filtering.
Internationalization: Full support for English and Turkish, with the infrastructure ready for additional languages.
I chose technologies that prioritize developer experience while delivering a production-ready product:
Next.js 16 (App Router): The latest Next.js with React Server Components provides excellent performance. The App Router's file-based routing and built-in API routes made the architecture clean and maintainable.
Supabase: Instead of manually setting up PostgreSQL, authentication, and storage, Supabase provides all of this out of the box. The Row Level Security (RLS) policies ensure users can only access their own data, which was critical for a multi-tenant application.
TypeScript + Zod: End-to-end type safety with runtime validation. Every form submission, API call, and database operation is validated against schemas defined with Zod.
Tailwind CSS + shadcn/ui: Utility-first styling combined with accessible, composable components. The entire UI is built with shadcn/ui components—no fighting with CSS specificity or component libraries that don't quite fit.
Zustand: For client-side state management, Zustand provides just enough structure without the boilerplate of Redux. I use it primarily for managing filter states and UI preferences.
next-intl: Internationalization is often an afterthought, but next-intl made it painless. All text is stored in JSON translation files, and switching languages doesn't require a page reload.
Row Level Security: Every database table has RLS policies that tie records to authenticated users. This happens at the database level, so even if there's a bug in my application code, users can't access each other's data.
Server Components by Default: Most of the application uses React Server Components, with client components only where interactivity is needed (forms, modals, drag-and-drop). This keeps the JavaScript bundle small and initial page loads fast.
Optimistic UI Updates: When you drag an application to a different status column, the UI updates immediately while the database update happens in the background. If the update fails, the UI rolls back automatically.
File Upload with Supabase Storage: Resume attachments are stored in Supabase Storage buckets with proper access policies. The file upload flow includes progress tracking and automatic cleanup of replaced files.
URL State Management: All filters and search queries are synced with the URL using nuqs. This makes the application feel more like a traditional web app—you can share links to specific filtered views or use the browser's back button as expected.
The core applications table is straightforward but comprehensive:
CREATE TABLE applications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id),
company_name TEXT NOT NULL,
position TEXT NOT NULL,
status TEXT NOT NULL,
work_type TEXT NOT NULL,
location TEXT,
salary TEXT,
source TEXT,
url TEXT,
recruiter_name TEXT,
recruiter_phone TEXT,
recruiter_email TEXT,
notes TEXT,
cover_letter TEXT,
resume_path TEXT,
is_pinned BOOLEAN DEFAULT FALSE,
applied_date DATE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
Every time a record is created or updated, I ensure the user_id matches the authenticated user. RLS policies enforce this at the database level.
One of my goals was to maintain a clean, maintainable codebase. Here's what that looks like:
Component Organization: The components directory is split into domain-specific folders (applications, auth, layout) and a ui folder for generic components. This makes it easy to find what you're looking for.
Server Actions: Instead of building REST endpoints, I use Next.js Server Actions for all data mutations. This reduces boilerplate and keeps related code together.
Type Safety Everywhere: From form validation to API responses, everything is typed. Zod schemas serve as the single source of truth for data structures.
Error Handling: Toast notifications provide immediate feedback for all user actions, whether successful or failed. Loading states are handled with Suspense boundaries.
Drag-and-Drop State Management: Implementing drag-and-drop with optimistic updates was trickier than expected. The kanban board needs to update immediately when you drag a card, but also needs to handle the case where the server rejects the update. I ended up using Zustand to manage the temporary UI state and rolling back on error.
File Upload UX: Supabase Storage doesn't support progress tracking out of the box. I had to implement custom upload logic with XMLHttpRequest to show upload progress and handle edge cases like interrupted uploads.
Internationalization with Server Components: Getting next-intl to work smoothly with the App Router required careful setup. The key was creating a proper locale provider that works with both server and client components.
RLS Policy Debugging: Writing Row Level Security policies is powerful but can be frustrating to debug. I learned to test policies thoroughly in the Supabase SQL editor before relying on them in production.
I believe in building in public and contributing back to the community. This project is MIT licensed and fully open source for a few reasons:
While JobTrack is fully functional, there are several features I'm considering:
The application is live at jobapplytracker.com, and the source code is available on GitHub.
Whether you're actively job searching or just curious about the tech stack, I'd love to hear your feedback. Feel free to open issues, submit PRs, or reach out to me directly.
Building JobTrack was both personally useful and technically rewarding. It solved a real problem I faced, and along the way, I deepened my understanding of Next.js, Supabase, and modern React patterns.
Key Takeaways:
Next time you're job hunting, give JobTrack a try. And if you're building something similar or have questions about any part of the stack, don't hesitate to reach out. 🚀