HomeArticles

When a MySQL database is enough: skipping the CMS for small content sites

The day I stopped installing WordPress

Five years ago, I'd spin up WordPress for any client site that needed a blog or a few editable pages. Then came the updates, the plugin conflicts, the security patches at 2 AM. I got tired of explaining why their "simple" site needed a 200 MB install. So I started asking a different question: what does this site actually need to do?

For a small content site — a local business with 10 pages, a photographer's portfolio, a community notice board — the answer is usually: a few text blocks, maybe a list of posts, and a contact form. That's it. No users, no comments, no e-commerce. That fits in a single MySQL table and about 200 lines of PHP.

Why MySQL and not SQLite?

I hear this a lot. SQLite is lighter, zero config, but it's a file — and files get corrupted, permissions get weird, and backups are messy. MySQL is a real server, but that's actually a feature: it handles concurrent reads, has proper user management, and your host already runs it. If you're on a $5 VPS or shared hosting, MySQL is already there. You're not adding a new dependency, you're using what's installed.

For a site with 50 pages and 100 visits a day, MySQL will yawn. I've run sites with 10,000 posts on a single table with no indexes and it was still fast enough. The key is to not overthink it.

The setup that works for me

Here's the pattern I've settled on after a dozen such sites. It's not a framework, just a folder structure and a few files.

  • content/ — folder with one PHP file per page, each returning an array of data (title, body, etc.)
  • admin/ — single password-protected script to edit content, stored in MySQL
  • public/ — the actual site, just index.php that queries the DB and renders

The trick is to keep content in the database, not in files. Why? Because then the admin script can update it without touching PHP code. I use a table like this:

CREATE TABLE pages (
  id INT AUTO_INCREMENT PRIMARY KEY,
  slug VARCHAR(100) UNIQUE,
  title VARCHAR(200),
  body TEXT,
  updated_at TIMESTAMP
);

That's it. No meta table, no revisions, no categories. For a small site, the slug is the URL — /about maps to WHERE slug='about'. The admin script does a simple UPDATE when you save.

The admin: one file, no framework

I used to build separate admin panels with login screens and roles. Now I just use a single PHP file with a hardcoded password hash. It's not fancy, but it works. Here's the core of it:

if ($_POST['password'] === getenv('ADMIN_PASSWORD')) {
  // show editor form
} else {
  // show login form
}

Yes, it's basic. But for a site where only the owner logs in, it's enough. I put the password in an environment variable, not in the code. The form lets you pick a page by slug, edit the title and body in a textarea, and save. That's literally the whole admin.

I use htmlspecialchars() on output and strip_tags() on input to keep it safe. No WYSIWYG. The client types plain text or simple HTML. They learn in five minutes.

Rendering the site

Public/index.php looks like this:

$slug = $_GET['slug'] ?? 'home';
$stmt = $pdo->prepare('SELECT * FROM pages WHERE slug = ?');
$stmt->execute([$slug]);
$page = $stmt->fetch();
if (!$page) { http_response_code(404); exit('Not found'); }
echo template($page);

The template function is just an HTML wrapper with the title and body. No router, no controller. I do have a small helper for the nav, which queries all slugs and titles to build a menu. That's it.

When it's not enough

I'm not saying do this for everything. If you need user accounts, complex workflows, or a REST API, use a real backend. But for a brochure site or a simple blog with no comments, the CMS is the heavy part. The content is light.

What about search? MySQL's LIKE '%term%' works fine up to a few thousand rows. If you need fuzzy search or faceted filtering, you'd move to something else. But I've never had that requirement on a small site.

Maintenance: the real win

The biggest benefit isn't speed or code size — it's maintenance. No updates to schedule, no dependencies to audit, no plugins to break. I can SSH in and grep the whole codebase in seconds. If something goes wrong, there's only one table to inspect.

I once had a client whose WordPress site got hacked because of an outdated plugin. I rebuilt it with this pattern in four hours. It's been up for two years with zero issues. The client doesn't care about the stack — they just want to edit their text and have it work.

Practical tips if you try it

  • Use prepared statements from day one, even for simple queries. It's habit.
  • Cache the nav in a static variable so you only query once per request.
  • Add an updated_at column and show it in the admin — helps trust.
  • Keep the admin behind a separate directory with HTTP auth if you can, not just PHP password.

If your site fits the pattern — small, few pages, one editor — give it a shot. You'll spend an afternoon building it and save weeks over the next three years.