How to Add Job Search to an Existing App
Not every integration is a new job board. Most are a job search or job listing feature bolted onto an existing product — a careers-content site adding a "browse open roles" tab, an HR tool showing market context next to a comp review, a community app surfacing relevant postings to members. This guide covers that case: adding job data to something that already exists, rather than starting from the Next.js starter template.
Decide What You're Actually Adding
Before writing any code, narrow the scope. Three common patterns:
- A search widget — a search box plus a results list embedded in an existing page. Usually just the
/jobslist endpoint with a handful of filters exposed to the user. - A scoped feed — a fixed set of listings for a specific segment (e.g. "Jobs at companies in our directory," "Remote roles in your industry"). Usually a server-side query with filters baked in, not exposed to the end user.
- Contextual enrichment — showing job data next to something else already in your UI (e.g. company salary context on a company profile page). Usually a handful of calls scoped by company or role, not a full search experience.
Each needs a different amount of surface area. Don't build full faceted search if you only need a scoped feed.
Minimal Integration: A Scoped Feed
If you're surfacing a fixed slice of listings (option two above), you don't need a client-side search UI at all — fetch server-side and render.
curl "https://api.cleanjobdata.com/jobs?title=engineer&remote=true&experience_level=SE&limit=20" \
-H "Authorization: Bearer $CLEANJOBDATA_API_KEY"Keep the API key server-side (a server component, an API route, or a backend service) — never call the jobs API directly from client-side JavaScript with your key embedded, since that exposes it to anyone viewing your page source.
// app/careers/page.tsx (Next.js server component example)
async function getJobs() {
const res = await fetch(
`${process.env.CLEANJOBDATA_API_BASE_URL}/jobs?title=engineer&remote=true&limit=20`,
{ headers: { Authorization: `Bearer ${process.env.CLEANJOBDATA_API_KEY}` } }
);
return res.json();
}Adding a Search Widget
If users need to type a query and adjust filters themselves, add a thin API route that forwards the request server-side so your key stays hidden, then call that route from the client.
// app/api/jobs/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const res = await fetch(
`${process.env.CLEANJOBDATA_API_BASE_URL}/jobs?${searchParams.toString()}`,
{ headers: { Authorization: `Bearer ${process.env.CLEANJOBDATA_API_KEY}` } }
);
return Response.json(await res.json());
}Your frontend then calls /api/jobs?title=...&remote=true — the same query params, just proxied. This keeps the integration small: no new backend service, no separate deployment, just one route added to an app that already exists.
Contextual Enrichment
For a single company or role lookup (e.g. showing open roles on a company profile page you already have), scope the query tightly rather than pulling a broad feed and filtering client-side. company_website_url filters /jobs to one employer by their real website domain — the most reliable way to scope a query to a specific company (don't confuse it with the separate domain param, which matches each job's application-URL domain — for many ATS-hosted boards that's the ATS's own hosting domain, not the employer's):
curl "https://api.cleanjobdata.com/jobs?company_website_url=acme.com&limit=5&fields=id,title,location,application_url" \
-H "Authorization: Bearer $CLEANJOBDATA_API_KEY"If you don't know a company's domain but have already seen one of their jobs in a prior response, that job's top-level employer_id field also filters /jobs exactly to that employer via the employer_id param — useful for a "more roles at this company" widget on a job detail page, without needing to know their domain up front. Keep the request cheap and the response small with fields, which matters when the job data is a secondary element on a page whose primary content is something else.
What Not to Build
- Don't build your own pagination cache or dedup layer for a small, fixed feed — that complexity is worth it for a full job board syncing thousands of listings, not for a 20-item widget.
- Don't expose your API key to the client under any circumstances, even for a low-traffic internal tool.
- Don't fetch the full default field set if you're only displaying title, company, and location — use
fieldsto request exactly what you render.
Adding job search to an existing product is usually a much smaller task than it looks: one server-side call (or a thin proxy route if the client needs to control the query), scoped to exactly what your UI displays.