If you can suppress your gag reflex, M4 can do some of what you want.
sinclude(...) will include a file and be silent in case of failure.
I did something like this for user-login history because I couldn't use
PHP or Perl:
me% cat index.m4
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Better user history</title>
<link rel="stylesheet" href="local.css">
</head>
<body>
<h3>Users</h3>
<ul>
<li><em>By username</em></li>
<ul>
sinclude(`byuser.m4')dnl
</ul>
<li><em>By last login date</em></li>
<ul>
sinclude(`bydate.m4')dnl
</ul>
</ul>
sinclude(`footer.m4')dnl
</body>
</html>
me% cat byuser.m4
<li><a href="byuser/A-L">A-L</a></li>
<li><a href="byuser/M-Z">M-Z</a></li>
me% cat bydate.m4
<li><a href="bydate/2025">2025</a></li>
<li><a href="bydate/2026">2026</a></li>
me% cat footer.m4
<p><em>Last update: syscmd(`date | tr -d "\n"')</em>
me% m4 index.m4 > index.htm
me% cat index.htm
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Better user history</title>
<link rel="stylesheet" href="local.css">
</head>
<body>
<h3>Users</h3>
<ul>
<li><em>By username</em></li>
<ul>
<li><a href="byuser/A-L">A-L</a></li>
<li><a href="byuser/M-Z">M-Z</a></li>
</ul>
<li><em>By last login date</em></li>
<ul>
<li><a href="bydate/2025">2025</a></li>
<li><a href="bydate/2026">2026</a></li>
</ul>
</ul>
<p><em>Last update: Mon Oct 12 03:08:32 EDT 2026</em>
</body>
</html>