Skip to content

Agency Website · Public Site + Admin CMS

Devxiom — Website & Admin CMS

My agency's website and the admin CMS behind it, built solo: a DB-driven public site that never hard-fails, a schema-driven CMS with drag-to-reorder and a rich-text blog editor, a lead inbox with in-dashboard replies, revocable server-side sessions and a full audit log.

  • The live Devxiom homepage hero in its default dark theme, with a light/dark toggle in the navbar.
  • A database-driven case study page on the live site, with breadcrumbs, tech pills and a project facts panel.
  • The admin leads inbox, where each enquiry can be replied to by email straight from the CMS. Demo data.
  • The blog post editor, with a rich-text body that is sanitised on save. Demo data.
  • The sessions page lists every signed-in device and lets the admin sign out any of them. Demo data.
On this page

The Problem

Devxiom is my software development agency. An agency site has an unusual job: it is the sales pitch and also a sample of the work, so it has to be fast, polished and well ranked. Behind the scenes it also has to work as a small business tool, catching every enquiry and letting me answer it quickly.

Off-the-shelf options pulled in the wrong directions:

  • Content changes all the time. Projects, services, pricing, testimonials, FAQs and blog posts change every few weeks. Editing code and redeploying for each change is too slow, and a heavy hosted CMS is too much for a one-person agency.
  • No enquiry can be lost. A contact form that silently fails, or an email that lands in spam, is lost business. Every lead has to be stored first and notified second, and it should be possible to reply from the same place.
  • The admin panel is a target. It controls the public face of the business, so it needs real session control, not just a long-lived cookie: see where you are signed in, sign out a device, and see who changed what.
  • Search is where clients come from. Services, case studies, reviews, FAQs and articles all need to be properly understood by search engines.
  • The public site must never break. If the database is unreachable, visitors should still see a complete site.

What I Built

I designed and built the whole thing on my own: a public agency website and a full admin CMS in one Next.js 14 (App Router) codebase, styled with Tailwind CSS and backed by MongoDB. It runs to about 14,500 lines of JavaScript, with 28 admin and public API routes, and is live at devxiom.tech.

Public website

The site has:

  • a homepage with a hero, trust strip, services, work, process, pricing, testimonials, FAQs, about section and contact
  • a services index and a page for each service
  • a work index and a case study page for each project
  • a blog with a page for each post

It has a light and dark theme toggle, reveal-on-scroll animations, an image lightbox, generated Open Graph images, a web app manifest, and custom error and not-found pages.

Content is read from the database and merged over built-in defaults. If the database is empty or unreachable, every section falls back to those defaults, so the public site never breaks. Everything that depends on the brand name (contact details, footer links, about profiles) is recalculated from the current brand rather than stored twice, so it can never go out of date.

A schema-driven admin CMS

The admin is built around one generic collection manager driven by field schemas, rather than a separate hand-built screen for each content type. Projects, services, pricing plans, testimonials, FAQs and blog posts all get:

  • forms generated from the schema
  • drag-and-drop ordering (dnd-kit)
  • a sticky save bar
  • a confirmation before anything is deleted
  • clear error states with a Retry button

Adding a new content type means describing its fields, not writing a new page. The other admin screens are:

  • Site content: brand, tagline, social links, hero, section headings and calls to action, with an advanced JSON editor for the more structured sections
  • Projects: several Cloudinary images per project with an in-browser cropper and a chosen cover image. Images removed in the editor are deleted from Cloudinary too, so storage does not fill up with orphans.
  • Blog: a custom rich-text editor with its own undo and redo history. Everything it produces is sanitised on the server before saving, because the toolbar is a convenience, not a security boundary.

Every save refreshes the public pages immediately.

A lead pipeline that does not drop enquiries

When someone submits the contact form:

  • the request is checked and rate limited per IP (5 per 10 minutes)
  • a hidden honeypot field quietly absorbs spam bots
  • the lead is saved to MongoDB before any email is attempted, so a mail failure never loses an enquiry
  • a notification goes to a list of addresses managed in the admin, through nodemailer over SMTP

In the leads inbox I can search, filter, mark leads read or archived, and reply straight from the dashboard. The reply is sent by email, the lead is marked as read, and the reply is written to the audit log.

Sessions and audit trail

Admin sign-in uses bcrypt-hashed passwords and a signed JWT cookie, but the cookie only carries an opaque session id. The real record lives in a server-side session store:

  • sessions expire after 7 days idle, with a hard limit of 30 days
  • signing out or "sign out this device" revokes a session on the server at once, whatever the token says
  • a sessions page lists every signed-in device with its browser, operating system and IP address, worked out locally from the user agent with no third-party lookup
  • the light or dark theme choice is remembered on the session

Login attempts are rate limited, and in production the app refuses to start without a strong signing secret. Every admin action, such as updating a project, replying to a lead or changing the password, is written to an audit log that can be filtered in the dashboard. Audit writes never block, so logging can never break the action it records.

Search engine optimisation

Each page has its own metadata and canonical URL, the site has a generated sitemap and robots file, and it is verified in Google Search Console. Structured data (JSON-LD) describes:

  • the agency as a ProfessionalService with an offer catalogue, address and contact point
  • each service, each case study and each blog post
  • FAQs as an FAQPage
  • reviews with an aggregate rating
  • breadcrumbs on every page below the top level

Outcomes

  • Live and run without a developer. devxiom.tech is in production, and every piece of content, from the hero line to pricing and the blog, is edited in the admin with no redeploy.
  • Enquiries are never lost. Each lead is saved before any email is sent, spam is filtered quietly, and replies go out from the same inbox.
  • Admin access I can actually control. Sessions are stored and revocable on the server, with a device list, idle and absolute expiry, and an audit trail of every change.
  • A public site that can't break. If the database fails, the site falls back to its built-in content instead of an error page.
  • Easy to extend. The schema-driven admin means a new content type is a field list, not a new screen.
  • Built for search. Rich structured data for the agency, its services, case studies, FAQs, reviews and articles, plus Search Console verification.
  • Built solo, end to end. Design, front end, admin, API, data model, email, security and SEO, delivered in a few weeks in June and July 2026, with refinements since.
All projects

Have a project in mind?

Whether it's an MVP, a dashboard, or a rescue mission on an existing codebase — let's talk about it.

Prefer to talk first? Book a free 30-min call (opens in a new tab)