The Include-Based Layout Pattern
EJS doesn't have built-in layout inheritance like some other engines, but you can achieve the same thing with includes. The idea is simple: have a base template that includes header and footer, and your page content gets injected in the middle.
<%- include('partials/header') %>
<%= title %>
<%= content %>
<%- include('partials/footer') %>
This gives you a consistent layout across all your pages without repeating the header and footer HTML.
The Body Pattern
For more flexibility, use a "body" pattern where your layout includes a variable content section:
<%- include('partials/header') %>
<%- body %>
<%- include('partials/footer') %>
Then in your route, you render the page content first and pass it to the layout. This separates your layout from page content completely.
Extending Layouts
You can create nested layouts by including one layout inside another. Start with a base layout, then create page-specific layouts that include it:
<%# base.ejs - the foundation %>
<%- include('head') %>
<%- body %>
<%- include('scripts') %>
<%# admin.ejs - extends base %>
<%- include('base', { body: `
<%- include('admin-header') %>
<%- adminContent %>
`}) %>
This creates a hierarchy: base layout → admin layout → specific page. Each level adds its own pieces.
Partial Headers and Footers
Break your layout into logical partials. Each partial handles one concern:
<%# partials/head.ejs %>
<%= title %>
<%# partials/footer.ejs %>
Keep each partial focused on one thing. Your main template stays clean and your code stays maintainable.