DEV Community


Posted on • Updated on

Build a static site generator with webpack

Write html contents using javascript template literals and output static html files on webpack compile time:

  • Use only HtmlWebpackPlugin, no additional plugins needed.
  • Automatically load the specific configuration for each page.
  • Router implementation, no .html extension in the url.
  • Support webpack-dev-server.

Source code


How to

Create a webpack entry point src/index.js.

console.log('Hello from index.js');
Enter fullscreen mode Exit fullscreen mode

Instead of creating html pages using files with .html extension, write the html markup using javascript template literal. Create some javascript files inside src/views/. Each of those files represent an html page. You can name those files whatever you want (for example page1.js, page2.js, page3.js and so on). Those files will be converted by HtmlWebpackPlugin in static .html files.

const page1 = `<h1>Page 1</h1>`;

module.exports = page1;
Enter fullscreen mode Exit fullscreen mode

Create a custom template for HtmlWebpackPlugin to dynamically load the correct page inside src/views/ folder.

  <%= require(`./src/views/${htmlWebpackPlugin.options.pageName}.js`) %>
Enter fullscreen mode Exit fullscreen mode

Setup a blueprint configuration for HtmlWebpackPlugin. Use this blueprint to create different HtmlWebpackPlugin configurations for each page.

const generatePage = ({
  path = '',
  template = './html-template.ejs',
} = {}) => ({
  plugins: [
    new HtmlWebpackPlugin({
      filename: `${path && path + '/'}index.html`,

module.exports = generatePage;
Enter fullscreen mode Exit fullscreen mode

Create a readFiles function which use nodejs to read all page files in the src/views/ directory and return their names.

// create an array list of all pages filenames (without extension)
const pageNames = [];
readFiles('./src/views', ({ name }) => pageNames.push(name));
Enter fullscreen mode Exit fullscreen mode

Loop through each page name and load their specific HtmlWebpackPlugin configuration.

const pages = => generatePage({
  title: pageName,
  // if 'page1' then filename is 'index.html'
  // otherwise filename is 'page2/index.html'
  // and so on for every other page
  path: pageName === 'page1' ? '' : pageName,
  // `pageName` is a custom HtmlWebpackPlugin option
  // which is loaded in html-template.ejs

module.exports = pages;
Enter fullscreen mode Exit fullscreen mode

Require all pages in webpack.config.js, you can use webpack-merge to compose multiple configurations.

const merge = require('webpack-merge');

module.exports = merge(...pages);
Enter fullscreen mode Exit fullscreen mode

If you run webpack the structure in the dist folder will looks like this:

│    index.html
│--- page2/
│         index.html
│--- page3/
Enter fullscreen mode Exit fullscreen mode

You can also implement a router in your app to navigate between pages. You don't have to add index.html at the end of your links. With this method you'll not see .html extension in the url.

const router = `
  <li><a href="/">Go to page1</a></li>
  <li><a href="/page2">Go to page2</a></li>
  <li><a href="/page3">Go to page3</a></li>

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Then edit your src/views pages to include the router.

const router = require('./partials/router');

const page1 = `<h1>Page 1</h1>`;

module.exports = page1 + router;
Enter fullscreen mode Exit fullscreen mode

Run webpack again and try to navigate between pages: or and so on.


You can't view your app via file:/// protocol because your router links are set as absolute path, you must use a local or a remote server.

HMR doesn't work. If you add HotModuleReplacementPlugin and true views files will not be connected to devServer.


You can do a lot more: import variables inside template literals, use loop to generate multiple html tags, add other options to HtmlWebpackPlugin to further customize html-template.ejs, and so on.

If you're using VS Code and want a better typing experience for writing html inside template literals, I suggest template literal editor extension.

This article is part of my learn-webpack repositories collection.

Discussion (3)

ansimorph profile image
Björn Ganslandt

Is there a way to use this approach with css components?
When I try, style loader always fails with: ERROR in Template execution failed: ReferenceError: window is not defined.

pldg profile image
Luca Author

Can you setup a minimal repository to view?

ansimorph profile image
Björn Ganslandt

I'll set up a minimal repo when I find time, but meanwhile I figured out what the problem is in case anyone trips over the same problem.

Style-loader expects a DOM, which isn't there yet in the context of HtmlWebpackPlugin rendering the pages:

Minicssextract has the same issue:

So this boils down to a SSR problem for which the mini-css-extract issue discusses some possible solutions.

I ended up going the conventional way and included the css in a js-entrypoint.