Skip to main content
Cover image for RoleReady

RoleReady

nextjs typescript postgresql better-auth chrome-extension saas

I was tired of tracking job applications in spreadsheets. The real problem wasn’t the tracking itself—it was that every job board worked differently. LinkedIn had one format, Indeed another, Greenhouse yet another. None of the existing tools handled them all.

So I built RoleReady, a Chrome extension that works on any job board.

Tech stack: Next.js 15 • TypeScript • PostgreSQL • Better-Auth • Zustand • Chrome Extension


How It’s Built

I chose the stack for performance and maintainability. Every decision had a specific reason.

Next.js 15 and Server Components

Next.js 15’s App Router and Server Components eliminated client-side data fetching waterfalls. Components receive ready-to-render data from the server, which improved initial page load times significantly.

PostgreSQL with Drizzle ORM

I picked Drizzle over Prisma for the transparent, SQL-like syntax. Full TypeScript support without the abstraction layer. Migrations are visible and controllable—I can see exactly what SQL is running.

Better-Auth for Authentication

Better-Auth handles multiple providers (Google, GitHub) and prevents duplicate accounts by automatically detecting email conflicts and linking them. The implementation was straightforward compared to other auth libraries I’ve used.

Server-Side Enrichment Pattern

The biggest performance win came from moving business logic to the server. Instead of fetching raw data and processing it on the client, components now receive enriched data ready to render. This eliminated 865 lines of duplicate frontend code.

// Server-side enrichment pattern
export const enrichJob = async (job: Job) => {
  const enriched = { ...job };

  enriched._computed = {
    deadlineUrgency: calculateUrgency(job.deadline),
    displayTitle: formatTitle(job.title),
    interviewProgress: calculateProgress(job.interviews),
    applicationAge: daysSince(job.createdAt),
  };

  return enriched;
};

Universal Chrome Extension

The hardest problem was extracting job information from different sites. I tried building site-specific DOM parsers—they were brittle. Every time LinkedIn changed their HTML, the extension broke.

The solution: capture the entire page and send it to a server-side AI for processing.

// universal-content.js - works on any job board
const pageContent = document.documentElement.outerHTML;
const metadata = {
  url: window.location.href,
  title: document.title,
  timestamp: Date.now()
};

chrome.runtime.sendMessage({
  type: 'extract_job',
  content: pageContent,
  metadata
});

This works on 15+ platforms (LinkedIn, Indeed, Greenhouse, Lever, etc.) without site-specific code. Zero maintenance overhead.


Zustand for State Management

I used Zustand for global state with optimistic updates. The UI responds instantly, rolling back automatically if the API call fails.

export const useJobStore = create<JobStore>()(
  persist(
    (set, get) => ({
      jobs: [],

      updateJobStatus: async (id, status) => {
        const originalJobs = get().jobs;
        // Update UI immediately
        set(state => ({
          jobs: state.jobs.map(job =>
            job.id === id ? { ...job, status } : job
          )
        }));

        try {
          await api.updateJob(id, { status });
          toast.success("Job updated");
        } catch (error) {
          // Rollback on error
          set({ jobs: originalJobs });
          toast.error("Failed to update job");
        }
      }
    }),
    { name: 'job-store' }
  )
);

The UI feels instantaneous. Perceived performance improved dramatically.


The Results

Deployed on Vercel with PostgreSQL. I avoided microservices and Kubernetes—simple, reliable infrastructure was the goal.

The performance gains were measurable:

  • Package installation: 45s → 3s (Bun)
  • Bundle size: -40% (tree-shaking)
  • Time to Interactive: 2.1s → 1.2s
  • Server response: -23%
  • Memory usage: -35%

The platform handles document management, interview tracking, and analytics to identify patterns in job application success rates.


Try it yourself: View Live DemoSource Code