Case study
Nextward
A jobs and internships platform for students and professionals, in English and French. It brings together roles that companies post and listings imported every day from job boards, shows who can apply to each one, and writes a tailored CV and cover letter for any role.
- Role
- Sole designer and developer
- Type
- Independent product
- Timeline
- 2021 – today
- Status
- Live
- Laravel
- Vue.js
- Inertia.js
- MySQL
- Tailwind CSS
- OpenAI & Claude APIs

Problem
People looking for internships and jobs in IT search across Tunisian job boards, company career pages and international boards. Many listings don’t say whether someone in another country can apply, or whether the role pays, sponsors a visa or helps with relocation, and they come in French or English. Companies need a simple place to post roles and talk to applicants.
Ownership
I build Nextward on my own. It started in 2021 as Internly, the final-year project of my Bachelor’s in Computer Science at ISSAT Sousse: an internship platform where students applied and companies posted roles.
In 2026 I rebuilt it as Nextward on Laravel 13: jobs as well as internships, professionals as well as students, daily imports from job boards, every page in English and French, and AI tools for CVs and cover letters.
Constraints
- I work on it alone, alongside contract work.
- Imported listings come from sites I don’t control, in different formats and languages, and often leave out who can apply.
- AI calls cost money, so the daily import can’t make one call per listing.
- Its first users are in Tunisia: payments in dinars go through local gateways (Konnect and Flouci), and international cards through Lemon Squeezy.
Architecture
Laravel serves Vue pages through Inertia.js, with server-side rendering for public pages so search engines can read the listings. The data lives in MySQL. Imports, translations, emails and alert digests run as queued jobs on a scheduler.
Every morning the importer reads Tanitjobs and Keejob, and employer boards on Greenhouse, Lever and Ashby. It follows robots rules and request limits, keeps listings from the last 30 days, and shows a role listed on two boards only once. Each listing keeps its source and the time it was last checked.
Before a listing is added, an LLM (OpenAI or Claude, through Laravel’s AI package) reads it in a batch of ten. It sets the field, type, seniority, work mode, skills and who can apply, and sends scams and pay-to-apply posts to a review queue for an admin.
Every listing has an English and a French version. The AI translates without rewriting the employer’s text, and the original stays one click away.
Candidates keep up to 10 resumes. The AI can fill in a profile from an uploaded resume and write a CV and cover letter for one listing, using only facts from the candidate’s profile. CVs come in six themes, print to PDF through headless Chrome, download as Word files and are laid out right to left in Arabic.
Listing pages carry JobPosting structured data and are listed in a sitemap. Companies post roles, review applications and message candidates from their own workspace.
Tradeoff
Most imported listings don’t say whether someone in another country can apply. Guessing from the location or the company would put more roles under the Open worldwide filter, and send people to roles they can’t take.
Instead, each listing records who can apply: anyone, listed countries, people already allowed to work in that country, or unknown. Open worldwide only shows roles open to anyone, and a listing says when the source didn’t give pay, visa or relocation details. The cost is a shorter worldwide list: some open roles stay out of that filter until the source or an admin says more.
Testing & reliability
- 306 PHPUnit tests in 51 files cover imports, applying, messaging, emails, credits, resumes, translations and the public pages. GitHub Actions builds the front end and runs them on every push.
- If the AI key is missing, the account is out of credit or a call fails, the importer uses keyword rules instead and the run carries on.
- A source pauses after three failed runs in a row, and rate-limited requests wait for the source’s Retry-After time.
- Payments are checked again with the provider before credits are added, and a failed AI call gives its credit back.
- Not done yet: there is no error monitoring beyond Laravel’s logs.
Outcome
Nextward is live at nextward.alabaganne.com. Anyone can browse and filter listings without an account, and candidates and companies can sign up. CV themes, Arabic CVs and paid credits are in the repository but not deployed yet.
Evidence
The screenshots below are public pages of the live site. Pages behind a login (the candidate dashboard, resumes, CV and letter kits, the company workspace and the imports admin) aren’t shown.
Screenshots
Finding roles
Anyone can search internships and jobs, in English or French, without an account.
(full-size image, opens in a new tab)Home page
Search by role, company or skill, with popular searches underneath.
(full-size image, opens in a new tab)Browse opportunities
Tabs for internships and jobs, one row of filters and an Open worldwide toggle.
(full-size image, opens in a new tab)In French
Every public page and listing also has a French version.
Listings
Each listing says where it comes from, when it was last checked and who can apply.
(full-size image, opens in a new tab)Listing page
A listing translated from French by AI, with a link to apply on the source site.
(full-size image, opens in a new tab)Who can apply
The original text in a fold-out, the countries that can apply, the source and its last check.
Joining
Candidates and companies sign up from the same form.
(full-size image, opens in a new tab)Sign up
Pick whether you’re looking for a role or hiring.
More case studies
- MenuMate
Independent product
- KartaTunisie
Independent product
- PromptStream
Independent product