In this article, we will guide you through creating a dynamic job board application using Next.js. A job board application helps job seekers browse, filter, and apply for job openings while enabling employers to post job listings.
Project Preview Build a Job Board with Next.js Prerequisites:Approach to Build a Job Board with Next.js:- Setup the Project by Creating a new NextJS project Install the necessary libraries.
- Create Layouts and Components using Bootstrap for responsive design.
- Create Navbar component for easy navigation across the application.
- Create AddJob component to add new job listings to board.
- Create JobListing component which is responsible to display the all listings.
- Add search feature to look for the specific job.
- Add filters to allow users to filter job listings by job type (full-time, intern, etc.) and experience level.
Steps to Build a Job Board with Next.js:Step 1: Create a application of NextJS using the following command.
npx create-next-app job-board Step 2: Navigate to project directory
cd job-board Step 3: Install the necessary package in your project using the following command.
npm install bootstrap Project Structure.png) Folder Structure Dependencies "dependencies": { "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", "bootstrap": "^5.3.3", "next": "14.1.3", "react": "^18", "react-dom": "^18", "react-icons": "^5.0.1" } Example: Create the required files in respective folder and write the following code.
JavaScript
// page.js
import React from 'react'
import JobListing from './components/JobListing'
const page = () => {
return (
<div>
<JobListing />
</div>
)
}
export default page;
JavaScript
// Nvabar.js
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import Link from "next/link";
function Navbar() {
return (
<nav className="navbar navbar-expand-lg navbar-light bg-light shadow top-0">
<div className="container">
<a className="navbar-brand text-success" href="#">
GFG Job Listing
</a>
<button
className="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNav">
<ul className="navbar-nav">
<li className="nav-item">
<Link className="nav-link" href="/">
Home
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" href="AddJob">
Add New Job Listing
</Link>
</li>
</ul>
</div>
</div>
</nav>
);
}
export default Navbar;
JavaScript
// JobListing.js
"use client";
import React, { useState } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import Navbar from "./Navbar";
import { FaLocationDot } from "react-icons/fa6";
const JobListing = () => {
const storedListings = localStorage.getItem("listings");
const allListings = storedListings ? JSON.parse(storedListings) : [];
const [listings, setListings] = useState(allListings);
const [filter, setFilter] = useState("all");
const [searchTerm, setSearchTerm] = useState("");
const [experienceFilter, setExperienceFilter] = useState("all");
const handleFilterChange = (e) => {
const value = e.target.value;
setFilter(value);
applyFilters(value, searchTerm, experienceFilter);
};
const handleSearchChange = (e) => {
const value = e.target.value;
setSearchTerm(value);
applyFilters(filter, value, experienceFilter);
};
const handleExperienceChange = (e) => {
const value = e.target.value;
setExperienceFilter(value);
applyFilters(filter, searchTerm, value);
};
const applyFilters = (jobTypeFilter, search, experience) => {
let filteredListings = allListings;
if (jobTypeFilter !== "all") {
filteredListings = filteredListings.filter(
(listing) => listing.jobType === jobTypeFilter
);
}
if (search) {
filteredListings = filteredListings.filter((listing) =>
listing.title.toLowerCase().includes(search.toLowerCase())
);
}
if (experience !== "all") {
filteredListings = filteredListings.filter((listing) => {
const [minExp, maxExp] = listing.experience
.split("-")
.map((exp) => exp.trim());
const minExpYears = parseInt(minExp);
const maxExpYears = maxExp
? parseInt(maxExp.replace(" years", ""))
: minExpYears;
if (experience === "0-1 years") {
return minExpYears <= 1;
} else if (experience === "1-3 years") {
return minExpYears <= 3 && maxExpYears >= 1;
} else if (experience === "3-5 years") {
return minExpYears <= 5 && maxExpYears >= 3;
} else if (experience === "5+ years") {
return minExpYears >= 5;
}
return true;
});
}
setListings(filteredListings);
};
return (
<>
<Navbar />
<div className="container mt-5">
<div className="row">
<div className="col-md-3">
<div className="mb-3">
<p>Type:</p>
<label
className="form-check-label me-3"
style={{ fontSize: "1rem" }}
>
<input
type="checkbox"
name="filter"
value="all"
checked={filter === "all"}
onChange={handleFilterChange}
style={{ transform: "scale(1.3)" }}
/>{" "}
All
</label>
<label
className="form-check-label me-3"
style={{ fontSize: "1rem" }}
>
<input
type="checkbox"
name="filter"
value="full-time"
checked={filter === "full-time"}
onChange={handleFilterChange}
style={{ transform: "scale(1.3)" }}
/>{" "}
Full Time
</label>
<label className="form-check-label" style={{ fontSize: "1rem" }}>
<input
type="checkbox"
name="filter"
value="intern"
checked={filter === "intern"}
onChange={handleFilterChange}
style={{ transform: "scale(1.3)" }}
/>{" "}
Intern
</label>
</div>
<div className="mb-3">
<label htmlFor="experience" className="form-label">
Experience
</label>
<select
className="form-select"
id="experience"
value={experienceFilter}
onChange={handleExperienceChange}
>
<option value="all">All</option>
<option value="0-1 years">0-1 years</option>
<option value="1-3 years">1-3 years</option>
<option value="3-5 years">3-5 years</option>
<option value="5+ years">5+ years</option>
</select>
</div>
</div>
<div className="col-md-9">
<div className="mb-3">
<label htmlFor="search" className="form-label">
Search
</label>
<input
type="text"
className="form-control"
id="search"
value={searchTerm}
placeholder="Python Developer..."
onChange={handleSearchChange}
/>
</div>
<div className="row">
{listings.map((listing) => (
<div key={listing.id} className="col-md-6 mb-4">
<div className="card h-100 w-100">
<div className="row g-0">
<div className="col-md-8 d-flex flex-column">
<div className="card-body d-flex flex-column">
<div className="d-flex align-items-center">
<img
src={listing.logoData}
className="rounded-circle
img-thumbnail"
alt={listing.title}
style={{
width: "70px",
height: "70px",
objectFit: "cover",
marginRight: "15px",
}}
/>
<div>
<h4 className="card-title mb-0">
{listing.title}
</h4>
</div>
</div>
<h5 className="card-title">
{listing.companyName}</h5>
<p className="card-text">
<FaLocationDot className="text-success" />
{listing.location}
</p>
<p className="card-text">
Experience Required:
{listing.experience}+ Yrs
</p>
<div className="d-flex
justify-content-between">
<p className="card-text">
{listing.jobType === "full-time"
? "Salary"
: "Stipend"}
</p>
{listing.jobType === "intern" ? (
<p className="card-text">
₹{listing.stipend}/Month
</p>
) : (
<p className="card-text">
₹{listing.salary} LPA/Annum
</p>
)}
</div>
<button className=
"btn btn-outline-success w-50">
Apply
</button>
</div>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</>
);
};
export default JobListing;
JavaScript
// pages/AddJob.js
import React, { useState } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import Navbar from "@/app/components/Navbar";
const AddJob = () => {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [salary, setSalary] = useState("");
const [stipend, setStipend] = useState("");
const [location, setLocation] = useState("");
const [logo, setLogo] = useState("");
const [logoData, setLogoData] = useState("");
const [jobType, setJobType] = useState("full-time");
const [companyName, setCompanyName] = useState("");
const [experience, setExperience] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
console.log({
title,
description,
salary,
stipend,
location,
logo,
logoData,
jobType,
companyName,
experience,
});
// Save the form data and image data to local storage
const jobListings = {
title,
description,
salary,
stipend,
location,
logo,
logoData,
jobType,
companyName,
experience,
};
const listings = JSON.parse(localStorage.getItem("listings")) || [];
listings.push(jobListings);
localStorage.setItem("listings", JSON.stringify(listings));
// Reset form fields after submission
setTitle("");
setDescription("");
setSalary("");
setStipend("");
setLocation("");
setLogo("");
setLogoData("");
setJobType("full-time");
setCompanyName("");
setExperience("");
};
const handleImageChange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onloadend = () => {
setLogoData(reader.result);
};
if (file) {
reader.readAsDataURL(file);
setLogo(file.name);
}
};
return (
<>
<Navbar />
<div className="container" style={{ width: "70%" }}>
<h2 className="mt-3 mb-4">Add New Job Listing</h2>
<form onSubmit={handleSubmit}>
<div className="row mb-3">
<div className="col-md-6">
<label htmlFor="companyName" className="form-label">
Company Name
</label>
<input
type="text"
className="form-control"
id="companyName"
value={companyName}
onChange={(e) => setCompanyName(e.target.value)}
required
/>
</div>
<div className="col-md-6">
<label htmlFor="title" className="form-label">
Title
</label>
<input
type="text"
className="form-control"
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>
</div>
<div className="row mb-3">
<div className="col-md-6">
<label htmlFor="jobloc" className="form-label">
Job Location
</label>
<input
type="text"
className="form-control"
id="jobloc"
value={location}
onChange={(e) => setLocation(e.target.value)}
required
/>
</div>
<div className="col-md-6">
<label htmlFor="exp" className="form-label">
Experience Required
</label>
<input
type="text"
className="form-control"
id="exp"
value={experience}
onChange={(e) => setExperience(e.target.value)}
required
/>
</div>
</div>
<div className="mb-3">
<label htmlFor="description" className="form-label">
Description
</label>
<textarea
className="form-control"
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
required
></textarea>
</div>
<div className="mb-3">
<label htmlFor="jobtype" className="form-label">
Select Job Type
</label>
<select
className="form-control"
id="jobtype"
value={jobType}
onChange={(e) => setJobType(e.target.value)}
required
>
<option value="full-time">Full Time</option>
<option value="intern">Internship</option>
</select>
</div>
{jobType === "full-time" && (
<div className="mb-3">
<label htmlFor="salary" className="form-label">
Salary
</label>
<input
type="text"
className="form-control"
id="salary"
value={salary}
onChange={(e) => setSalary(e.target.value)}
required
/>
</div>
)}
{jobType === "intern" && (
<div className="mb-3">
<label htmlFor="stipend" className="form-label">
Stipend per Month
</label>
<input
type="text"
className="form-control"
id="stipend"
value={stipend}
onChange={(e) => setStipend(e.target.value)}
required
/>
</div>
)}
<div className="mb-3">
<label htmlFor="logo" className="form-label">
Logo
</label>
<input
type="file"
className="form-control"
id="logo"
accept="image/*"
onChange={handleImageChange}
required
/>
</div>
{logoData && (
<img
src={logoData}
alt="Selected"
className="img-fluid mb-3"
style={{ maxWidth: "200px" }}
/>
)}
<button type="submit" className="btn btn-primary">
Add Job to Portal
</button>
</form>
</div>
</>
);
};
export default AddJob;
Start your application using the following command:
npm run dev Output
 Job Board
|