We needed 175+ SEO-optimized category pages. React SSR felt overkill. Next.js felt like a framework dependency we didn’t need. So we built SSR with raw Express.js and template literals.
Here’s why it works better than you’d think.
The Architecture
Each category page at MisuJob is a fully server-rendered HTML page with:
- Unique
<title>,<meta description>,<h1> - CollectionPage JSON-LD schema
- BreadcrumbList schema
- Paginated job listings (30/page)
- 100+ internal links in the footer
No React. No hydration. No client-side JavaScript for the initial render.
The Config-Driven Approach
const CATEGORY_CONFIG: Record<string, CategoryDef> = {
'python': {
title: 'Python Developer Jobs in Europe | MisuJob',
description: 'Find Python developer jobs...',
h1: 'Python Developer Jobs',
whereClause: "'Python' = ANY(skills) OR title ILIKE '%python%'",
keywords: 'python jobs, django, flask, fastapi'
},
'remote': {
title: 'Remote Tech Jobs | MisuJob',
description: 'Browse remote tech positions...',
h1: 'Remote Tech Jobs',
whereClause: "remote_type = 'remote'",
keywords: 'remote jobs, work from home, distributed'
},
// ... 173 more categories
};
One Express route handles all 175 categories:
router.get('/jobs/:category/', async (req, res, next) => {
const config = CATEGORY_CONFIG[req.params.category.toLowerCase()];
if (!config) return next(); // Fall through to job detail route
// whereClause comes from our hardcoded config, NEVER from user input.
// If you adapt this pattern, use parameterized queries for any dynamic values.
const jobs = await readPool.query(`
SELECT id, title, company, location, skills
FROM jobs
WHERE is_active = true AND visibility = 'public'
AND (${config.whereClause})
ORDER BY posted_date DESC NULLS LAST
LIMIT 31 OFFSET $1
`, [offset]);
const html = renderCategoryPage(category, config, jobs);
res.setHeader('Cache-Control', 'public, max-age=3600, s-maxage=21600');
res.send(html);
});
Why Not React SSR?
Zero JavaScript needed. These are read-only listing pages. There’s no interactivity. Shipping React for static content is waste.
Template literals are fast.
\${title}
`` renders in microseconds. No virtual DOM diffing.175 pages, same template. The config-driven approach means adding a new category is 5 lines of config, not a new component.
Cache-friendly. Static HTML with aggressive
Cache-Controlheaders. CDN serves most requests without hitting the server.
Performance Results
| Metric | Value |
|---|---|
| Time to first byte | < 100ms |
| HTML size | 15-25 KB |
| Lighthouse Performance | 95+ |
| Client-side JS | 0 KB |
| Categories | 175+ |
The SEO Payoff
Google has indexed 19,700 pages and counting. Each category page generates internal links to job pages, and the footer creates a web of 100+ cross-links between categories.
Browse the result: Remote Jobs, Python Jobs, Jobs in Berlin.
Do you use a framework for SSR or roll your own? We’d love to hear your approach.

