Jun 10, 2024
static-site-express is a simple Node.js based static-site generator that uses EJS and Markdown. Deploy your static site to Netlify or any platform to your liking. Suited for landing pages, portfolio, blogs, documentation, hobby projects.
Click on "Use this template" button to get an exact copy of the repo / site builder. Then use the master branch,
which is the default. Or use the GitHub CLI:
gh repo create your-username/new-repo -p webandras/static-site-express
Note: Netlify will build your site from the default branch (usually the master) by default.
You can use a different branch other than the default one, but in that case Decap CMS (previously: Netlify CMS) will
not work properly. For
example, the images uploaded through the CMS will be pushed into the default branch, not the other one you set up in
Netlify!)
Test website: Use the 'Deploy to Netlify' button at the project's website to have a test website.
First, install or update npm packages.
Second, create a .env file (see .env.example), and set the variables.
If you want to use Algolia Search, you need
to register and generate your API credentials. If
you don't want to use Algolia, set enableSearch to false in config/site.config.js.
Check out all the settings in the site.config.js. There are comments there with additional information.
./content into the ./public folder (in watch mode):npm run watch-chokidar
Or:
npm run watch-nodemon
If you modify site.config.js restart the watchers to apply the changes you have made.
For local development, make sure you rewrite the mode to "development"!
Generate the js and css bundles as well (in --watch mode): npm run webpack-watch
localhost:4000 (or the port you set in .env, default port is 4000) (legacy):npm run serve
Switch to browser-sync to have live reloading in the browser when files change:
npm run liveserver
or run:
browser-sync start --server 'public' --files 'public'
npm run webpack watcher script to make sure the js and css bundles are recreated after file changes.If you don't see your changes:
npm run watch-chokidar, or npm run watch-nodemon,npm run webpack,npm run liveserverMake sure to build the live bundle in production mode.
The JavaScript source is in the app/ folder. Generally, you only need to modify 2 sections of the core/generator.js:
.ejs) in the pages/ folder, and a template (in layouts/) to be used for that
page (or use one of the pre-existing templates like default.ejs).templateConfig object literal.After the changes, restart build/watch scripts. This process in suboptimal, but currently this is the workflow.
content/ folder)posts/) where the front matter block contains the post properties (you can
change them, but do not forget to update the templateConfig object literal (generator.js) as well).pages/) are using templates and partials defined in the layouts/ folder.config/site.config.js file contains some of the global site properties (like site title, author, description,
social media links etc.) that are used in the EJS partials. Can also be extended it to your liking.netlify.toml configuration file contains important properties:[build]
base = "/"
publish = "public"
command = "npm run build"
The base path, the build command, and the "publish" directory. You can keep those settings unchanged.
You can also define here some post-processing actions to be run in the post-processing stages, for example as part of Netlify's CI/CD pipeline.
In the optional _headers file you can specify the HTTP headers and set Content Security Policy (CSP) rules for the
Netlify server.
Currently, CSP rules are commented out. You can also specify these in netlify.toml.
The _redirects file is currently empty. When you have a custom domain, you can make a redirect from .netlify.com to
your custom domain there.
robots.txt default settings:
# Disallow admin page
User-agent: *
Disallow: /admin/
# Disallow message-sent page
User-agent: *
Disallow: /message-sent/
# Rule 3
User-agent: *
Allow: /
For Google Search Console verification, you should have an HTML file
from Google included in the root of your Netlify publish folder (in our case, public). The build script copies this
file from ./content to ./public.
Add the name of the filename in the filesToCopy array at line 100 in ./app/core/generator.js and restart watch
script!
Netlify builds your website with its buildbot. It starts a Docker container running the Netlify build image
When the Docker fires up, this script runs (unfortunately, Netlify moved the build image to a private repository, so this is archived now): https://github.com/netlify/build-image/blob/focal/run-build.sh
This is the Dockerfile from which the Netlify image is built (based on the ubuntu image):
https://github.com/netlify/build-image/blob/focal/Dockerfile
Netlify automatically discovers the contact form via custom netlify attributes added to the form. A bot field is present in the form to protect against spam bots. Netlify has first-class spam filter.
These are the key parts in the code for Algolia:
const algoliasearch = require("algoliasearch");
const client = algoliasearch(process.env.ALGOLIA_APP_ID, process.env.ALGOLIA_ADMIN_KEY);
const index = client.initIndex(process.env.ALGOLIA_INDEX);
Use the AlgoliaSearch client library to send request to update and/or create records for the posts:
index.partialUpdateObjects(searchIndexData, {
createIfNotExists: true,
});
This is currently the structure of the search index (as a default example):
searchIndexData.push({
/**
* The object's unique identifier
*/
objectID: postData.attributes.date,
/**
* The URL where the Algolia Crawler found the record
*/
url: canonicalUrl,
/**
* The lang of the page
* - html[attr=lang]
*/
lang: config.site.lang,
/**
* The title of the page
* - og:title
* - head > title
*/
title: postData.attributes.title,
/**
* The description of the page
* - meta[name=description]
* - meta[property="og:description"]
*/
description: postData.attributes.excerpt,
/**
* The image of the page
* - meta[property="og:image"]
*/
image: config.site.seoUrl + "/assets/images/uploads/" + postData.attributes.coverImage,
/**
* The authors of the page
* - `author` field of JSON-LD Article object: https://schema.org/Article
* - meta[property="article:author"]
*/
authors: [config.site.author],
/**
* The publish date of the page
* - `datePublished` field of JSON-LD Article object: https://schema.org/Article
* - meta[property="article:published_time"]
*/
datePublished: postData.attributes.date,
/**
* The category of the page
* - meta[property="article:section"
* - meta[property="product:category"]
*/
category: postData.attributes.topic || "",
/**
* The content of your page
*/
content: postContents,
});
Note: Currently the objectID is the post publish date (like "2022-08-17"). Maybe better to change it to the whole
slug to be completely unique. You can't have two posts at the same day now.
Provided by i18next package. See in assets/js/main.js. Remove this feature if you don't need it. Some texts (like
the ones coming from config) cannot be made translatable. Probably not a good solution. The code is left there mainly
for reference.
The translations come from content/lang/translations.json.
Example usage 1:
<h1 data-i18n="contact.title">Contact page</h1>
Example usage 2:
<textarea name="message"
data-i18n="[placeholder]contact.message_placeholder"
placeholder="Your message..."
></textarea>
Credits: © Michael Lee. GitHub His website/blog
When I started my journey as web developer, I started using Jekyll for my simple websites after reading some articles from Michael Lee about it. He has a great starter for Jekyll, the Jekyll ⍺.
The data comes from content/data/opening-hours.yml. It can be edited from Decap CMS as well.
Bug: Problem with an existing folder.
The build script should always delete the folders inside the public folder.
However, the assets folder is sometimes not deleted, so an exception occurs:
[Error: EEXIST: file already exists, mkdir './public/assets']
nodemon not trigger re-build on Linux on file changes (this behavior was experienced on Ubuntu 18.04 LTS Bionic
Beaver)npm run watch-exp command which uses the chokidar
package.If you have a problem or a question about static-site-express, open an issue here.
The idea of using a Node.js static site generator came from this good article by Douglas Matoso (not accessible any more): Build a static site generator in 40 lines with Node.js.
This package uses some modified code parts from doug2k1/nanogen (mainly from
the legacy branch and some ideas from the master branch, MIT © Douglas Matoso 2018).
MIT licence - Copyright © 2018-2026 András Gulácsi.