Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Shared page parts

All blog pages share the same header, menu and footer. Copying them into every template is a bad idea: every change would have to be made in all files. A partial is a piece of HTML in its own file that is inserted into other templates with the include() function.

Create the views/partials/ folder with two files. The header views/partials/header.ejs:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title><%= title %> — <%= siteName %></title>
  <link rel="stylesheet" href="/css/style.css">
</head>
<body>
  <header>
    <a href="/posts"><%= siteName %></a>
    <nav>
      <a href="/posts">Posts</a>
      <a href="/about">About</a>
    </nav>
  </header>
  <main>

And the footer views/partials/footer.ejs:

  </main>
  <footer>The Express blog, 2025</footer>
</body>
</html>

The header opens the <html>, <body> and <main> tags, and the footer closes them.

Including them

Rewrite views/posts/index.ejs so that only the page content remains:

<%- include('../partials/header') %>
 
<h1><%= title %></h1>
<ul>
  <% for (const post of posts) { %>
    <li><a href="/posts/<%= post.id %>"><%= post.title %></a></li>
  <% } %>
</ul>
 
<%- include('../partials/footer') %>

The path in include() is relative to the folder of the current template, and the extension can be omitted. The <%- tag is needed here: a template is ready HTML and must not be escaped. Open /posts and the page gets the menu and the footer, while the browser tab reads "All posts — The Express blog".

The post page

The second template is built the same way. Create views/posts/show.ejs:

<%- include('../partials/header') %>
 
<article>
  <h1><%= post.title %></h1>
  <p><%= post.body %></p>
</article>
<a href="/posts">All posts</a>
 
<%- include('../partials/footer') %>

In the show controller replace the response with res.render('posts/show', { title: post.title, post }). The /posts/1 page gets the same header and footer.

Passing data to a partial

A partial sees all the variables of the page that included it. You can also pass your own as the second argument:

<%- include('../partials/postCard', { post }) %>

This makes it easy to extract a post card views/partials/postCard.ejs and use it in the list, in search results and on the home page.

Common mistake

The partial uses the title variable, so it must be passed to every res.render(). If you forget, the template fails with title is not defined. Either always pass title, or set a default in middleware: res.locals.title = 'Blog' — then pages without their own title get the shared one.

Thanks for reading!

Donate to the site — we work hard for you.