Fast-EJS is a simple and fast tool to pre-render EJS templates into static HTML files with clean conventions and flexible data handling.
npm install fast-ejs-builderindex.html generation for clean URLs
bash
npm install -g fast-ejs-builder
`
$3
`bash
npm install fast-ejs-builder --save-dev
`
Quick Start
1. Initialize your project:
`bash
mkdir my-site && cd my-site
`
2. Create your project structure:
This is the recommended structure. You can use your own.
`txt
my-site/
├── components/ # Reusable EJS components
├── data/ # Global and local data files
└── pages/ # EJS templates
│ └── public/ # Static assets
└── fast.ejs.json # Configuration file
`
3. Configure your site in fast.ejs.json:
`json
{
"build": {
"output": "build",
"interval": 100,
"useIndexRouting": true
},
"components": {
"dir": "components",
"autoGenerate": false
},
"data": {
"dir": "data",
"allow": "all"
},
"pages": {
"dir": "pages"
},
"tailwind": {
"output": "public/app.css",
"imports": []
}
}
`
4. Start development:
`bash
fast-ejs dev
`
5. Build for production:
`bash
fast-ejs build
`
$3
- Add fast-ejs-builder to your dev dependencies
`bash
npm install fast-ejs-builder --save-dev
`
- Add the build and dev scripts in your package.json
`json
{
"scripts:": {
"dev": "fast-ejs dev",
"build": "fast-ejs build"
}
}
`
- Run your package
`bash
npm run dev
npm run build
`
Configuration
The fast.ejs.json (occasionnally called _the FEJ_) file controls all aspects of your site generation. Here's a complete configuration example:
`json
{
"build": {
"output": "build",
"interval": 100,
"useIndexRouting": true
},
"components": {
"dir": "components",
"autoGenerate": false
},
"data": {
"dir": "data",
"allow": "all"
},
"pages": {
"dir": "pages"
},
"tailwind": {
"output": "public/app.css",
"imports": [
"public/style.css",
"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
]
}
}
`
$3
- build.output: Directory where generated HTML files are saved
- build.interval: Milliseconds between rebuilds in development mode
- build.useIndexRouting: Generate route/index.html instead of route.html
- components.dir: Directory containing reusable EJS components
- components.autoGenerate: Generate missing components. Default is false
- data.dir: Directory for global and page-specific data files
- data.allow: Data file format ("js", "json", or "all")
- pages.dir: Directory containing your EJS page templates. Here is where you should mainly work.
- tailwind.output: Path to generated Tailwind CSS file
- tailwind.imports: Array of external CSS URLs to include. Each @layer will be detected if specified.
Usage Examples
$3
Create EJS templates in your pages.dir directory:
`ejs
<%= title %>
<%- include('../components/header') %>
<%= title %>
<%= description %>
<%- $('footer') %>
`
$3
Create reusable components in your components.dir:
`ejs
`
And use it inside a page or another component by calling:
`ejs
<%- $("header","Some arg") %>
`
Here $0 has the value of "Some arg". You can pass many args and access them through $1,$2,...\
Remember that only components can access args data $x.
$3
#### 1. Using base data
Fast EJS comes with default data that can't be overrided.
- $ : _function_\
Imports a component by its name. Like a 'super include'.
`ejs
<%- $("users/avatar","https://placehold.co/400") %>
`
- $0,$1,... :\
Return the args passed to a component. Can be accessed only inside a component, not a page.\
In the previous example, we can access "https://placehold.co/400" by using $0 (0 for the first arg).
`ejs
`
- $async : _Promise function_\
Asynchronously imports a component.
`ejs
<%- await $async("dashboard") %>
`
- $route :\
Returns the current route relative to the pages.dir. In this example, it will return /home
`ejs
<%= $route %>
`
- $css :\
Automatically imports the relative path of generated tailwind css from tailwind.output inside a page. No need to manually write the css path and change everytime.\
\
For example, inside pages/users/profile.ejs, it can return something like ../../public/app.css\
while inside pages/index.ejs, it will return something like ./public/app.css
`ejs
<%- $css %>
`
- $date :\
Returns a new Date object.\
Stop writing year manually.
`ejs
<%= $date.getFullYear() %>
`
- $env : _function_\
Get a env variable from process.env. Useful to build env based pages.
`ejs
<%= $env("NODE_ENV") %>
`
- $cls : _function_\
Same behavior as tailwind clsx. Useful to write conditions based classes.
`ejs
<%= $cls(isActive && "text-primary", "bg-gray-100") %>
`
- $if : _function_\
Returns a value based on a condition or a default value if set. Can also works with components :=)
`ejs
<%- $if(isActive, $("active-bar")) %>
<%= $if($env("NODE_ENV")=="prod", "Hello","World") %>
`
- $debug : _function_\
Prints anything in the console during build. Use it to debug your pages or components
`ejs
<%- $debug("Header component says hi !") %>
`
- $upper,$lower and $trim : _functions_
Utils for strings.
`ejs
<%- $trim($upper(user.name)) %>
`
#### 2. Using your own data
Fill data files in data.dir (generated automatically if missing).\
Remember that using js data allow you to use function, async function and getter as data.
- Global data : Can be accessed inside every pages and components
If data.allow is all or js (recommended)
`javascript
// data/global.js
module.exports = {
siteName: "My Awesome Site",
navItems: [
{ label: "Home", url: "/" },
{ label: "About", url: "/about" },
{ label: "Contact", url: "/contact" },
],
add: (a, b) => a + b,
getUsers: async () => await db.getUsers(),
get randomBool() {
return Math.random() > 0.5;
},
};
`
If data.allow is all or json
`js
// data/global.json
{
"siteName": "My Awesome Site",
"navItems": [
{ "label": "Home", "url": "/" },
{ "label": "About", "url": "/about" },
{ "label": "Contact", "url": "/contact" },
],
}
`
- Local data : Can be accessed only in the target page
`javascript
// data/local.js
module.exports = {
// for page "pages/users/profile.ejs"
"users/profile": {
title: "Welcome to My Site",
description: "This is a fast-ejs powered website with Tailwind CSS.",
},
// dynamic routes
"user/$id": (params) => ({
id: params.id,
username: params.name,
}),
"blog/$id": ({ title, id }) => ({
id,
blogTitle: title,
}),
};
`
- Route params : Params sent to dynamic routes. Should return an array of object
`javascript
// data/route.js
module.exports = {
"user/$id": [{ id: "u1", name: "John Doe" }],
"blog/$id": () => {
const arr = [];
for (let i = 0; i < 5; i++) {
arr.push({ title: "Article " + i, id: i, isPremium: i % 2 == 0 });
}
return arr;
},
// or getter
get "blog/$id"() {
// ...
},
};
`
Note that all JS data can be asynchronous, and each asynchronous data will affect the build time.
Use cases
You can use fast-ejs to build any static website. Just keep in mind that all pages are pre-built, not in runtime like front-end frameworks.\
For example, if you want to create a blog, you have to use dynamic routes (ex: articles/$id.ejs) in order to generate each article page at once:
`js
// - Create "articles/$id.ejs" with the logic
// - Inside "data/route.js", add all articles with their data
module.exports = {
// ...,
"articles/$id": async () => {
const list = await db.getArticles();
return list.map((a) => ({ ...a, id: a._id })); // ensure "id" key
},
};
// - Add your view data inside "data/local.js"
module.exports = {
// ...,
"articles/$id": (article /represents the data from route.js/) => {
const { name, date } = article;
return { name, date }; // if only name and date are used in the view
},
};
/** Then, when the build is done, route.js will generate files based on your list. And each files built with the corresponding data.
* /articles/1 (retrieve data "articles/$id" but with the article with id=1 )
* etc..
*/
`
Commands
- fast-ejs dev: Start development server with live reloading
- fast-ejs build: Build static files for production
How it works
Here is the real building flow :
- Get the data inside data.dir that match the data.allow.
- Scan the specified pages.dir.
- All empty folder will be ignored
- If any .ejs file is found, search for used component inside components.dir.
- If a component is missing, it will be generated if components.autoGenerate is true
- If the file it is dynamic file (ex: $id.ejs), search for corresponding routes inside data.dir/route.js(json)
- Render the html with the corresponding data and generate the right file inside build.output depending on build.useIndexRouting.
- For any other file, just copy it inside the build.output.
- Generate the css with tailwind at tailwind.output along with tailwind.imports if specified.
- Scan the build.output and clean all junk files and folders (files from previous build and empty folders).
Notice that all empty folders inside pages.dir will be ignored in the build.
Usage tips
- Prefer using JS data
By setting data.allow to json, you allow only json data. But in real case you'll mostly need js data because you can write logic inside.\
By example, using this:
`json
{
"year": 2026
}
`
is less effective that using:
`js
module.exports = {
year: new Date().getFullYear(),
};
`
because in the second case, year can change at build time.\
Also js data can be async which is useful to get data from a database. So prefer setting data.allow to all or js when you need dynamic data.
- Duplicate data
- If data.allow is all, js data are prioritized over json data.
In the previous example, if year is in both js and json file, the js one will be used.
- You can't override base data. For example, declaring $route will have no effect.
- Don't misuse EJS tags
`ejs
<% $("header")%>
<%= $css %>
<%- user.name %>
`
Using <% means you're not expecting an output but $("header") should return a component.
Using <%= means you're expecting an escaped value but $css requires to be unescaped.
Using <%- means you're expecting an unescaped but user.name may return a string.
- Don't put components or data directory inside the pages directory
Any file inside the pages dir is considered a static file cause this directory is scanned to find ejs files and other files to generate an output.
pages.dir contains files that will be built (ejs, images, css, etc..)
components.dir contains parts of pages. Not pages.
data.dir contains data that will be used by the pages.
Therefore, adding components dir inside the pages dir, means components will be generated as pages and data dir files will be generated as static files.
- Fast EJS is a builder / generator. Not a framework
Because of some features like access of the current route ($route) or usage of async data, you might think you're using a kind of framework. But no. You're basically coding ejs templates with 'super powers', and something is generating the html and css files for you.
When someone visit your site, he will just see the static files early built. Even if some of your data are retrieved at build time, they can't change once the build is done.
So, you're not using a big framework that will run your SaaS.\
Please consider using this for small projects like static portfolios or landing pages.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository
2. Create your feature branch (git checkout -b feature/amazing-feature)
3. Commit your changes (git commit -m 'Add some amazing feature')
4. Push to the branch (git push origin feature/amazing-feature`)