The live Alphinex Solutions homepage leads with a 24-hour website delivery pitch, an email capture and key delivery stats. The live services page lays out each offering as a card with its feature checklist and a link to a dedicated detail page. In the admin dashboard, portfolio projects are managed as cards with cover images and reordered by drag and drop. Demo data. Job applications are reviewed per opening, with status tracking, a cover letter and short-lived signed links to the private CV. Demo data. Every image upload in the admin passes through a built-in cropper that locks the aspect ratio before the file is stored. Demo data.
On this page
The Problem
Alphinex Solutions sells software development: websites delivered in 24 hours, mobile apps, cloud, AI automation and design. For a company like that, its own website is the first piece of work a prospect sees. It has to load fast, rank in search, and show current projects, the current team and open jobs, and the people running the company need to keep all of that up to date without asking a developer each time.
The first version of the site had the right public pages, but the content behind them was hard to run:
- Content had to be easy to change. Projects, team members, testimonials, blog posts and job openings change every few weeks, and every change should be a form in an admin panel, not a code change and a redeploy.
- Hiring brings in private files. A careers page collects CVs, which hold personal data. They cannot simply sit at a public URL.
- Images arrive in every shape. Team photos, project screenshots and blog covers were uploaded at random sizes and aspect ratios, which broke the layout of the grids they appear in.
- Search visibility is the point of the site. An agency that sells websites needs its own pages to be understood properly by search engines: services, jobs, blog posts, reviews and the business itself.
- Admin screens must not fail silently. A failed request in the admin panel showed an empty list, which looks exactly like "no data" and invites someone to re-enter content.
What I Built
The site is a Next.js 14 (App Router) and TypeScript application with a public marketing site and a protected admin dashboard in one codebase, styled with Tailwind CSS. Two developers worked on it. The public pages and the first admin panel came first, and my part was turning it into a CMS the company can run itself:
- moving its data layer to MongoDB
- adding Cloudflare R2 storage with private files
- building the image pipeline, the storage manager and the settings screens
- the search engine work, and the error handling across the admin panel
It is live at alphinex.com, with roughly 14,000 lines of TypeScript, 25 pages and 25 API routes.
Public website
The public site has these pages:
- a homepage with the 24-hour website offer and an email capture
- a services index and a detail page for each service
- projects with a detail page each
- a team page and testimonials
- a blog with a page for each post
- careers with an online application form
- contact, privacy and terms
Projects, team, testimonials, blog posts and jobs are read from the database, so whatever is published in the admin appears on the site without a deploy. A site-wide consultation link, set in the admin, drives every "Get Free Consultation" button. Visitors can also reach the company through WhatsApp.
Moving the data layer to MongoDB
The site originally ran on Prisma over a hosted Postgres database. I moved it to MongoDB with Mongoose in a single pass:
- nine models (users, projects, team, testimonials, blogs, jobs, applications, contact recipients and site settings) sharing one set of base options
- a cached connection that survives hot reloads and serverless cold starts
- every API route rewritten against the new models, and a seed script that creates the admin account and starter content
Mongoose creates collections and indexes on first write, so a new environment needs no migration step: set the connection string, run the seed, and the site works.
Cloudflare R2 storage, with private CVs
All uploaded media now lives in Cloudflare R2, organised into folders: team photos, blog covers, project images, testimonial photos and applicant CVs. Images are public. CVs are private objects:
- the public application form can upload a CV, but no one can browse to it
- an admin who opens an application gets a signed link that expires after five minutes, to view in the browser or download
- uploads are checked against allowed file types, accepting either the browser's reported type or the file extension, because browsers are inconsistent about PDF and Word types
An image pipeline that keeps layouts clean
Every image upload in the admin goes through a built-in cropper before it is stored. The cropper:
- locks the aspect ratio for that slot (16:9 for project images, a round mask for team photos)
- lets the editor drag to reposition and zoom with the mouse or a slider
- exports a JPEG at most 1600 px wide
When several images are chosen at once they are queued ("3 images left to crop"), and the editor clicks one to make it the cover. Because images are fixed when they are uploaded, the grids on the public site always line up and pages never load oversized originals.
Admin dashboard
- Projects, team and testimonials: add, edit and delete each one, with drag-and-drop ordering saved straight back to the database. Projects can also carry optional demo access details.
- Blog: posts with slug, excerpt, Markdown body, cover image, author, category, tags, ordering and a published switch
- Careers: job openings with department, location, type, experience and salary. Each opening shows how many people have applied.
- Applications: each applicant's contact details, portfolio and LinkedIn links, cover letter and private CV, with a status of pending, reviewed, shortlisted or rejected
- Contact recipients: who receives contact-form emails, sent through Resend or SMTP, with a test-send button
- Storage: R2 usage against a set quota (used, remaining and file count), files grouped by folder, plus bulk delete and signed previews for private files
- Settings: change password, and the site-wide consultation link
Sign-in uses NextAuth with credentials, and middleware protects every admin route. Deletions go through a confirmation dialog. Every list has an explicit error state with a Retry button, so a failed request is never mistaken for an empty table.
Search engine optimisation
Each page has its own title, description and Open Graph tags, and the site generates a sitemap and robots file. Structured data (JSON-LD) describes:
- the business itself: an Organization and LocalBusiness with address, contact points and opening hours
- the site, with a search action
- each service, as a Service with offers
- each job opening, as a JobPosting
- the blog and each post
- breadcrumbs on deeper pages
- reviews, with an aggregate rating
Search engines can then show the company, its services, its open roles and its articles as rich results.
Outcomes
- Live and run by the company itself. alphinex.com is in production, and projects, team, testimonials, blog posts, jobs and the consultation link are all managed from the admin dashboard with no developer involved.
- Hiring handled on the site. Jobs are posted, candidates apply online, and CVs stay private behind links that expire after five minutes.
- Consistent visuals by design. Every image is cropped to its slot and resized when it is uploaded, so layouts never break on an awkward photo.
- Storage under control. All media sits in Cloudflare R2, with usage, quota and clean-up visible in the admin.
- A simpler data layer. Moving from Prisma and Postgres to MongoDB removed the migration step, and a fresh environment is a connection string plus one seed command.
- Built for search. Per-page metadata, a sitemap and rich structured data for the business, its services, jobs and articles.
- Admin screens that never fail silently. Every list has an error state with a retry, and deletions ask for confirmation first.