A website template for the modern web.
Powerful developer experience meets lightweight output.
Effortless Static Site Generation with Flexibility
Feeling overwhelmed by the static site generator landscape? Refo offers a refreshingly simple and customizable approach built entirely on Node.js.
Unlike Jekyll, Gatsby, Astro and others, we let you leverage the power of Node.js modules directly. This means you can generate any kind of website you can imagine, all with the flexibility of your favorite Node.js libraries and servers.
Key benefits:
- Effortless Development: Edit your modules and see instant updates thanks to hot reloading.
- Unmatched Flexibility: Import
SVG
s, utilizeraw
imports, andstyle
your components with ease. - Data-Driven Content: Craft resumes, portfolios, or any data-driven website with ease.
- Highly Customizable: No rigid folder structures, minify class names, or even swap out SolidJS for React – it's entirely up to you.
Go beyond the limitations of traditional static site generators. Embrace the power and flexibility of Refo for your next project!
⭐️ Star to support our work!
Get notified about new releases via emails.
- (Hot) Module Reloading using dynohot
- JavaScript eXtensible markup language using and babel-preset-solid
- Node.js module customization
- importing SVGs as components
raw
imports- (Java)Script bundling
- Extensionless imports using specifier-resolution-node
- Styled components using Emotion
- Short class names (like
a
,b
,c
, ...,aa
,ab
, ...) - Class name labels in
development
mode using stack-tracer
- Short class names (like
- Image dimensions setting using image-size
- HTML and inline CSS and JS minification using HTMLMinifier terser
- (Java)Script minification using UglifyJS
- Client side navigation
- Link prefetching using Quicklink
- Lazy loading using lazysizes
Markdown
support for strings inJSON
files with markdown-it- Duration calculation with Moment.js
PDF
generation using Puppeteer with chrome-finder
- Edit your resume data in a
JSON
file. - View and publish your resume as a
PDF
, anHTML
document
and or as a page on a website.- Design and customize resume layout with
HTML
andCSS
.
- Design and customize resume layout with
- Generate 1 or more
PDF
s supporting different formats likeLetter
andA4
.- Refresh the
PDF
after saving changes to see the up to datePDF
.
- Refresh the
- Initial steps
- Install , and .
- Download or clone this repository.
- Open a command prompt in this folder.
Install dependencies:
pnpm install
Are you on some kind of Unix based system? Mac? Linux? If so you might want to change the
port
in theindex
module, which is set to80
which works on Windows. Superstatic's default is3474
so you can remove it if you prefer.
Start the server in development mode:
pnpm dev
Visit http://localhost/ to access the website.
Generate a static site:
pnpm static
Open the index.html
within the static
folder to access the website.
import |
generated file | |
---|---|---|
index/ | static / |
|
• favicon.ico (icon file (Node.js module)) |
• favicon.ico |
|
• main.js .js (Node.js module ) |
→ | • main.js |
• index.html .jsx (Node.js module ) |
• index.html |
|
firebase.json .js (Node.js module ) |
firebase.json |
The imported
file
s (which have a certain file extension (ico
,png
)) (Node.js) modules) copy the files themselves into thestatic
folder when the modules are loaded. In module relading mode they remove them if they are not imported anymore.
The
default
export
of (Node.js)module
s (which have a certain file extension (js
,json
,html
) in their base file name) are written as the contents of the output files (into thestatic
folder). The full file names of the output files are the base file names of the (Node.js)module
s.
⭐️ Star to support our work!
index.html
.jsx
(import
ed module
):
import template from '#@SolidJS/template'
import use from '#@style'
const [{styled}, extract] = use()
const Body = styled.body`
font-weight: bold;
`
export default <>
{template(`<!DOCTYPE HTML>`)}
<html lang="en">
{template(`<head>`)}
<style>{extract()}</style>
{template(`</head>`)}
<Body>
example content
</Body>
</html>
</>
index.html
(generated file):
<!DOCTYPE HTML><html lang=en><head><style>.a{font-weight:700}</style></head><body class=a>example content</body></html>
You can deploy the static docs
folder as it is.
You might want to change the prefixum
in the following files according to the name of your project site
repository:
index/index/site/
main/
- header/index.jsx#L6
- index.jsx#L7
index/resume/index/index
You can completely remove the prefixum
in case you are publishing a user or an organization site
.
- Initial steps
- Remove the
prefixum
from the files listed above under GitHub Pages Deployment. - Install and set up CLI.
- Add a
.firebaserc
file with your Firebase project ID:{ "projects": { "default": "<projectId>" } }
- Remove the
Deploy your site to Firebase Hosting:
pnpm deploy
It can be useful to separate the resume template and publish it as a new Refo package.
Open a new issue if you think so and let's discuss this. We can definitely implement this if it turns out to be useful.
This example uses Refo's JSON handler. So you can control how and whether certain properties are displayed from the index/index/site/index/resume/data.js file as described in Refo's readme at the JSON handler Usage section.
This project uses superstatic to serve the generated static files. You can use any similar library to serve the files or no library at all in case you would like to browse the files directly. This can be useful for offline documentations for example.
You can remove superstatic and use firebase-tools instead (which uses superstatic) if you prefer. In this case, you can modify the scripts
in the package.json
file and replace superstatic
with firebase serve
commands.
This project uses concurrently to run Refo in watch mode and serve the files with superstatic. You can use any similar library like npm-run-all to run Refo and a server in parallel or no library at all if you don't need a file server.
The firebase.json
file could be named as superstatic.json
if you prefer. This template does not depend on Firebase itself. However, they provide one of if not the consistently fastest static hosting solution.
JavaScript template literals are used for templating HTML documents.
This example also uses common-tags in certain templates which allows using a shorter syntax in many cases.
Here are some scenarios commonly used in this example:
By default you can display an optional value and use a conditional operator to prevent displaying false values like undefined
for example:
module.exports = `
${item ? item : ''}
`
Common-tags does this for you. So you can use a shorter syntax with a tagged template literal:
const {html} = require('common-tags')
module.exports = html`
${item}
`
By default you can display an optional template part and use a conditional operator to prevent displaying false values like undefined
for example:
module.exports = `
${item ? `
<div>
` + item + `
</div>
` : ''}
`
With common-tags you can use a simple condition with a logical operator to achive the same:
const {html} = require('common-tags')
module.exports = html`
${item && `
<div>
` + item + `
</div>
`}
`
By default you can join
the result when looping through an array of items to prevent displaying commas between the returned items:
module.exports = `<section>
${items.map(item => `
<div>
${item}
</div>
`).join('')}
</section>`
Common-tags does this for you. So you can use a shorter syntax:
const {html} = require('common-tags')
module.exports = html`<section>
${items.map(item => `
<div>
${item}
</div>
`)}
</section>`
When you are not using a tagged template literal with common-tags or with a similar library, then you can concatenate template parts with the +
operator if you prefer:
module.exports = `
<div>
` + item + `
</div>
`
Or you can use a placeholder with the ${expression}
syntax instead:
module.exports = `
<div>
${item}
</div>
`
In some cases, one of these can be easier to read than the other so you may use the style according to the context or you can choose one over the other and stay consistent. This example uses both.
Some code editors like Atom and GitHub, for example, highlights html
tagged template literals as HTML as you can see this above as well.
- Install Package Control and JS Custom.
- Go to
Preferencies / Package Settings / JS Custom / Settings
. - Edit the
JS Custom.sublime-settings — User
file:{ "configurations": { "jsx": true , "custom_templates": { "styled_components": true , "lookaheads": { "\\<": "scope:text.html.basic" , "\\.|height|padding|margin": "scope:source.js.css" , "import|minify|await|export|if|window|\\(|,": "scope:source.js" } , "tags": { "injectGlobal": "scope:source.js.css" , "css": "scope:source.js.css" , "html": "scope:text.html.basic" } } } }
Now you can use the JS Custom - Default
syntax highlight option for JavaScript files.
The JSON handler is a standalone package. It is mainly useful to handle resume related data, but you can use it for anything else too.
You can use it as you can see in the example (asset/resume/getHandledJson.js) as well:
const handleJSON = require('refo-handle-json')
var json = JSON.parse(JSON.stringify(require('./data')))
json = handleJSON(json)
It is recommended to create a copy of the required JSON using the JSON.parse(JSON.stringify(json))
functions for example when you are using Refo in watch
mode (related comment), because the JSON hander is changing object properties.
The JSON handler is parsing string object values as Markdown
using markdown-it. Example: example/asset/resume/data.json#L7
Properties which are ending with -private
are removed. Example: example/asset/resume/data.json#L4
Objects which have a property named private
are removed too.
Properties which are ending with -full
are only included when a second true value parameter is passed to the handler function. Example: example/asset/resume/data.json#L8, example/asset/resume/getHandledJson.js#L9
Objects which have a property named full
are only included when a second true value parameter is passed to the handler function.
When an object contains a startDate
property without an endDate
property then a hidePresent
property can be used to hide a present label and show the current year instead.
A hideEndDate
property can be used to hide the current year shown instead of a present label.
A hideDuration
property can be used to hide the calculated duration. Otherwise, a duration
property is defined with the calculated duration (examples: 7 months, 1 year, 1.5 years, 2 years).
It can be useful to create in-depth documentation about each Refo package.
Open a new issue if you think so and let's discuss this. We can definitely implement this if it turns out to be useful.
- Oengi.com – Erik Engi's website and resume.
Propose file change to add your project here.
Resume + portfolio = Refo