Server Rendering in MPAs — Generating HTML on the Server
In this tutorial, you will learn about Server Rendering in MPAs. We cover key concepts, practical examples, and best practices to help you master this topic.
Server rendering in MPAs generates complete HTML documents on the server for each request, delivering fully-formed pages with content, styles, and structure directly to the browser.
What You'll Learn
By the end of this tutorial, you will understand how server rendering works in MPAs, the request-response cycle, template engines for generating HTML, server-side data fetching, and how to structure server-rendered applications.
Why It Matters
Server rendering is the foundation of MPAs. Understanding how the server generates HTML is essential for building fast, SEO-friendly, and maintainable web applications. It influences everything from page load speed to content management and caching strategies.
Real-World Use
Wikipedia renders every page on the server. When you request an article, the server fetches the content from a database, applies templates, and returns a complete HTML page. This approach allows Wikipedia to serve billions of requests efficiently with minimal client-side JavaScript.
Server Rendering Flow
┌─────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐
│ Browser │ │ Web │ │ Template │ │ Database │
│ │ │ Server │ │ Engine │ │ │
└────┬────┘ └─────┬──────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
│ Request URL │ │ │
│───────────────>│ │ │
│ │ Fetch data │ │
│ │─────────────────────────────────>│
│ │ │ │
│ │ Data returned │ │
│ │<─────────────────────────────────│
│ │ │ │
│ │ Render │ │
│ │ template │ │
│ │────────────────>│ │
│ │ │ │
│ │ HTML output │ │
│ │<────────────────│ │
│ │ │ │
│ Complete HTML │ │ │
│<───────────────│ │ │
│ │ │ │
│ Browser │ │ │
│ renders page │ │ │
└────────────────┘ └────────────────┘
Think of server rendering like a restaurant kitchen. The customer (browser) orders a specific dish (URL). The chef (server) reads the recipe (template), gathers ingredients (data from database), cooks the meal (renders HTML), and serves it on a plate (complete HTML page). The customer receives a finished dish, not raw ingredients.
Template Engine Example
// Express.js with EJS template engine
const express = require('express');
const app = express();
app.set('view engine', 'ejs');
app.set('views', './views');
// Route that renders a server-side page
app.get('/products/:id', async (req, res) => {
try {
const product = await db.products.findById(req.params.id);
const relatedProducts = await db.products.findRelated(product.category);
res.render('product', {
title: `${product.name} — My Store`,
product: product,
relatedProducts: relatedProducts,
user: req.session.user || null,
categories: await db.categories.findAll()
});
} catch (error) {
res.status(404).render('error', {
title: 'Product Not Found',
message: 'The product you requested does not exist.'
});
}
});
// views/product.ejs template
// <!DOCTYPE html>
// <html>
// <head>
// <title><%= title %></title>
// <meta name="description" content="<%= product.metaDescription %>">
// </head>
// <body>
// <%- include('partials/header') %>
// <main>
// <h1><%= product.name %></h1>
// <p class="price">$<%= product.price %></p>
// <p><%= product.description %></p>
// <%- include('partials/reviews', { reviews: product.reviews }) %>
// </main>
// <%- include('partials/footer') %>
// </body>
// </html>
Server-Rendered Form Handling
// Server-rendered form with validation
app.get('/contact', (req, res) => {
res.render('contact', {
title: 'Contact Us',
formData: {},
errors: {}
});
});
app.post('/contact', async (req, res) => {
const { name, email, message } = req.body;
const errors = {};
if (!name || name.trim().length < 2) {
errors.name = 'Name must be at least 2 characters';
}
if (!email || !email.includes('@')) {
errors.email = 'Please enter a valid email address';
}
if (!message || message.trim().length < 10) {
errors.message = 'Message must be at least 10 characters';
}
if (Object.keys(errors).length > 0) {
// Re-render form with errors and submitted data
return res.status(422).render('contact', {
title: 'Contact Us',
formData: req.body,
errors: errors
});
}
await db.contacts.create({ name, email, message });
// Redirect after successful submission (PRG pattern)
res.redirect(303, '/contact/thank-you');
});
Common Mistakes
- Putting business logic in templates. Templates should only handle presentation. Keep data fetching, validation, and business logic in route handlers or service layers.
- N+1 queries in templates. Fetching data in loops within templates causes N+1 database queries. Fetch all required data before rendering.
- Not handling errors gracefully. Every route should handle database errors, missing data, and validation errors. Return appropriate HTTP status codes and user-friendly error pages.
- Serving large assets through the server renderer. Static files (images, CSS, JS) should be served directly by a web server or CDN, not through the application's Rendering Pipeline.
- Mixing concerns in route handlers. Follow the MVC pattern: routes handle HTTP, controllers contain logic, models handle data, views handle presentation.
Practice Questions
- What happens during the server rendering cycle from request to response?
- How do template engines help organize server-rendered pages?
- What is the Post-Redirect-Get (PRG) pattern and why is it important?
- How do you handle validation errors in server-rendered forms?
- Why should templates not contain business logic?
Challenge: Build a product catalog MPA with Express.js"Express" >}}.js and EJS. Include a product listing page with pagination, a product detail page with related products, a search page with form validation, and a 404 error page. All pages must be fully server-rendered with data from an in-memory database.
FAQ
Mini Project
Build a 5-page blog MPA with Express.js and EJS templates. Include a home page with the latest 5 posts, a blog listing page with pagination (10 posts per page), a blog post detail page with comments, a search page with form validation, and an about page. Use partials for the header, footer, and post card. Implement proper error handling for 404 and 500 errors.
What's Next
You understand server rendering. Now learn about Page Transitions in MPAs and how navigation works.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro