Back to articles

Building a Job Board in an Afternoon with Next.js and JobsDataAPI

CleanJobData Engineering

Building a job board from scratch usually means building far more than a UI. You need job ingestion, search, filters, pagination, location normalization, salary parsing, SEO pages, and a way to keep listings fresh. JobsDataAPI removes most of that infrastructure by giving you a normalized jobs API that can power a Next.js job board directly.

What You Get

The API returns stable job objects instead of raw ATS-specific responses. A typical job includes:

  • title
  • location
  • locations
  • application_url
  • published
  • has_remote
  • employment_type
  • salary_min
  • salary_max
  • salary_currency
  • salary_text
  • experience_level
  • experience_levels
  • company
  • optional description

That structure is enough to build cards, detail pages, filters, SEO landing pages, and analytics without writing custom parsers.

Starter Architecture

A simple Next.js job board can be built around three layers:

  1. API client — fetches jobs from JobsDataAPI with Authorization: Bearer <key>.
  2. Server actions — call the API from the server and pass typed data to the UI.
  3. Next.js pages — render listing pages, detail pages, feeds, and SEO content.

This keeps API keys server-side and lets the frontend work with clean JSON.

Basic Job Request

curl "$CLEANJOBDATA_API_BASE_URL/jobs?title=frontend%20engineer&remote=true&experience_level=SE&location=US&limit=20" \
  -H "Authorization: Bearer $CLEANJOBDATA_API_KEY"

The response shape is:

{
  "data": [],
  "pagination": {
    "limit": 20,
    "next_page": "opaque-cursor",
    "prev_page": null
  },
  "meta": {
    "query_time_ms": 42,
    "filters_applied": []
  }
}

Use next_page and prev_page for cursor pagination. Preview keys may omit pagination tokens by design.

Rendering a Job Card

type Job = {
  id: number;
  title: string;
  location: string | null;
  salary_min: number | null;
  salary_max: number | null;
  salary_currency: string | null;
  experience_level: string | null;
  company: {
    name: string;
    logo: string | null;
  } | null;
};

function formatSalary(job: Job) {
  if (!job.salary_min && !job.salary_max) return null;
  const currency = job.salary_currency ?? "USD";
  const min = job.salary_min ? Intl.NumberFormat("en-US", { style: "currency", currency }).format(job.salary_min) : null;
  const max = job.salary_max ? Intl.NumberFormat("en-US", { style: "currency", currency }).format(job.salary_max) : null;
  return [min, max].filter(Boolean).join(" - ");
}

export function JobCard({ job }: { job: Job }) {
  return (
    <article>
      <h2>{job.title}</h2>
      <p>{job.company?.name}</p>
      <p>{job.location}</p>
      {formatSalary(job) ? <p>{formatSalary(job)}</p> : null}
      {job.experience_level ? <p>{job.experience_level}</p> : null}
    </article>
  );
}

Keep the presentation flexible because salary and company data can be missing depending on the source.

Adding Filters

The same API powers your search and filter UI:

curl "$CLEANJOBDATA_API_BASE_URL/jobs?title=engineer&city_id=123&remote=true&experience_level=MI,SE&salary=100000,180000&sort_by=relevance" \
  -H "Authorization: Bearer $CLEANJOBDATA_API_KEY"

Useful query parameters:

  • title or search
  • city_id, state_id, country_id, or location
  • remote=true
  • experience_level=EN,MI,SE,EX
  • salary=min,max
  • published_after=2026-06-01
  • max_age=7d
  • sort_by=published or sort_by=relevance

The backend validates these filters and returns the applied filters in meta.filters_applied.

Detail Pages

Request the description only when you need it:

curl "$CLEANJOBDATA_API_BASE_URL/jobs/123?fields=title,location,description,company,application_url,published,salary_min,salary_max" \
  -H "Authorization: Bearer $CLEANJOBDATA_API_KEY"

The detail route should link to application_url for the actual employer application flow.

SEO and Feeds

Normalized data makes SEO pages easier. You can generate pages for:

  • remote jobs
  • senior engineer jobs
  • city-specific feeds
  • state-specific feeds
  • salary-filtered feeds
  • company-domain feeds

Each page can use the same API filters and render a unique intro, problem statement, and FAQ from your content catalog.

Production Checklist

Before launching, verify:

  • API key is stored server-side, never in client bundles.
  • Pagination uses next_page and prev_page.
  • Missing salary, company, and location values are handled gracefully.
  • Expired jobs are excluded by default.
  • Location filters use city_id, state_id, country_id, or location country codes.
  • Detail pages request description only when needed.
  • SEO pages use stable slugs and canonical job data.

The Fast Path

JobsDataAPI gives you the data layer for a modern job board: normalized jobs, reliable filters, cursor pagination, and optional descriptions. Pair that with a Next.js App Router frontend and you can ship a useful job board without building scrapers, parsers, or a search infrastructure from scratch.