There is something strange about modern web development.
You create a page.
You write some content.
You add a few styles.
And somehow, before you can see your website, you need a build pipeline, a dependency tree, a framework, a bundler, configuration files, and a collection of abstractions standing between you and the thing you actually wanted to build.
Sometimes that complexity is justified.
For applications, large frontends, and highly interactive products, it can be essential.
But what about a portfolio?
A blog?
Documentation?
A personal website?
A content-driven site that mostly needs to do one thing well:
serve the content.
That question is what led me to build JPROT.
JPROT takes a different approach
JPROT is a Node.js site generator designed around a simple idea:
Write content. Customize the site. Run it.
No build step is required.
Your pages live as Markdown files inside content/.
content/
├── about.md
├── projects/
│ ├── jprot.md
│ └── aeroci.md
└── blog/
└── first-post.md
You write:
---
title: "My Project"
description: "A short description"
---
## My Project
This is my project.
Save the file.
Refresh the browser.
The page exists.
There is no compilation ceremony standing between the content and the website.
The website is not the configuration file
One thing I wanted to avoid was turning the configuration file into the website itself.
JPROT keeps those responsibilities separate.
The configuration describes the site:
export default {
title: 'Your Name',
tagline: 'Full Stack Developer',
description: 'A short SEO description',
url: 'https://yoursite.com',
lang: 'en',
author: 'Your Name',
nav: [
{ label: 'About', url: '/about' },
{ label: 'Blog', url: '/blog' },
],
}
The Markdown describes the content.
The theme describes the presentation.
Components describe reusable UI.
Plugins extend behavior.
That separation makes the system easier to understand and, more importantly, easier to change.
Start with one command
Creating a JPROT project is intentionally boring.
And I mean that as a compliment.
npm create jprot@latest my-site
Then:
cd my-site
npm start
Your development server starts at:
http://127.0.0.1:4114
You can also initialize an existing directory:
jprot init --portfolio
or:
jprot init --docs
or:
jprot init --resume
The point is not to create another complicated project generator.
The point is to get from empty directory → working website quickly.
Markdown is the content layer
JPROT uses Markdown because content should remain easy to read without the framework.
A page is still a page.
A blog post is still a blog post.
A project is still a project.
You don't need to wrap every paragraph in components just to make the content render.
But Markdown doesn't mean the system has to be primitive.
JPROT supports frontmatter, shortcodes, placeholders, project conventions, blog conventions, and components.
So you can keep writing:
---
title: About Me
description: Who I am and what I build.
---
# Hello
I build software.
while still having a structured site underneath.
Five levels of customization
This is where JPROT starts to become more than a Markdown renderer.
There are five independent ways to customize a site.
1. CSS variables
Change the design directly through:
theme/custom.css
No framework-specific styling system is required.
2. Component overrides
Need a different header?
Replace it.
Need a different homepage?
Replace it.
Need a completely different layout?
Override the component.
3. Markdown components
Components don't necessarily have to be JavaScript.
You can create a Markdown component with frontmatter and placeholders.
theme/components/
This keeps simple UI pieces close to the content model.
4. Themes
Ready-made themes can be copied into the project and customized.
5. Plugins
When you need behavior rather than styling, plugins provide an extension point.
This is where JPROT deliberately keeps its surface small.
A plugin is just a file
A plugin can look like this:
export default {
name: 'analytics',
setup({ on }) {
on('html:head', (html) =>
html.replace(
'</head>',
` <script defer src="/_a.js"></script>\n</head>`
)
)
},
}
Then:
export default {
plugins: ['./plugins/analytics.js'],
}
The plugin API exposes a deliberately small surface:
addComponent
addRoute
extendMarkdown
on
config
That constraint is intentional.
A plugin system becomes difficult to maintain when every internal implementation detail becomes part of the public API.
I would rather have a smaller API that can remain stable.
JPROT is not only a generator
One of the things that changed as I built JPROT was the scope of the project itself.
It started from a very simple idea:
generate a portfolio.
But the architecture naturally grew toward something broader.
The same system can handle:
- portfolios
- documentation
- blogs
- project pages
- resumes
- personal websites
- content-driven sites
The important part is not the category.
It is the underlying model:
Content
↓
Content Graph
↓
Routes
↓
Rendering
↓
Website
That is why I no longer think of JPROT simply as a portfolio template.
It is becoming a general-purpose system for content-driven websites.
The Content Graph
One of the internal ideas behind JPROT is the Content Graph.
Instead of treating every Markdown file as an isolated document, JPROT builds a parsed index of the site's content.
That gives the system a unified view of:
- pages
- posts
- projects
- routes
- metadata
- navigation
This makes features such as navigation, routing, and content-aware tooling much easier to reason about.
The website stops being a folder full of Markdown files.
It becomes a structured content system.
Development without waiting for a build
One of my favorite parts of the workflow is also one of the simplest.
Create:
content/test.md
Write:
---
title: Test Page
---
# Hello
Save.
Refresh.
Done.
The development server watches the project, so you don't need to manually rebuild the entire site just to see a content change.
For a content-heavy website, this feels surprisingly natural.
The browser becomes the immediate reflection of the files you are editing.
But JPROT still has a build
This sounds contradictory.
It isn't.
JPROT doesn't require a build step for development.
But it can export the entire site as static HTML:
jprot export --out dist
That means you can develop dynamically and publish statically.
The workflow becomes:
write
↓
run JPROT
↓
preview
↓
export
↓
deploy
So the build exists when it is useful — not because development cannot function without it.
SEO is part of the website
A website generator shouldn't make you rebuild basic web infrastructure from scratch.
JPROT includes support for:
- metadata
- canonical URLs
- sitemap generation
- feeds
- JSON-LD
- PWA support
- configurable language and direction
The goal is not to turn SEO into a separate project.
It should be part of producing a website.
Accessibility should not be an afterthought
There is another area where I didn't want the "simple website generator" approach to become an excuse for cutting corners.
JPROT includes accessible SPA navigation features such as:
- skip links
- focus management
- live page announcements
The site can behave like a modern SPA while preserving the fundamentals that make navigation understandable to assistive technologies.
That distinction matters.
Fast navigation is useful.
Accessible navigation is necessary.
SPA navigation without turning everything into a JavaScript application
This is another design decision behind JPROT.
A content site doesn't necessarily need to become a giant client-side application.
JPROT uses server-side rendering while providing SPA-style navigation.
The result is a useful middle ground:
Server-rendered content
+
client-side navigation
+
normal URLs
You get fast transitions without having to turn every page into a client-side data-fetching application.
TypeScript without forcing TypeScript
JPROT itself ships TypeScript declarations.
That means a JavaScript configuration can still receive editor assistance:
/** @type {import('jprot').JprotConfig} */
export default {
title: 'My Site',
}
You don't have to convert the entire project to TypeScript just to get useful type information.
The tooling adapts to the project instead of forcing the project to adapt to the tooling.
The CLI is part of the system
JPROT isn't only:
jprot
The CLI covers the complete workflow.
jprot init
jprot new post "My Article"
jprot g component Hobbies --palette cards
jprot search
jprot add SomeComponent
jprot check
jprot lint
jprot export
There are also site-aware checks.
For example:
jprot lint
can look for things such as broken links, missing metadata, and duplicate anchors.
And:
jprot check
validates configuration and plugins.
That means errors can be found before deployment rather than discovered by a visitor.
And then there is the editor
I didn't want the workflow to end at the terminal.
JPROT also has a Visual Studio Code extension.
It provides:
- live preview
- JPROT Markdown highlighting
- frontmatter highlighting
- shortcode support
- placeholder highlighting
- snippets
- project-aware preview
The preview is driven by the project's own JPROT server.
So the editor isn't running a second, approximate implementation of the site.
It is looking at the same system the project actually uses.
JPROT can also be used as a library
This is perhaps the direction I find most interesting.
You don't have to use JPROT only through the CLI.
It exposes a programmatic API:
import {
createJprot,
exportSite,
runLint,
runCheck,
scaffoldSite,
} from 'jprot'
For example:
const app = await createJprot({
root: '/path/to/project',
port: 3000,
watch: true,
})
await app.listen(3000)
Or export a site programmatically:
await exportSite({
root: '/path/to/project',
outDir: '/tmp/dist',
})
That opens a different door.
JPROT doesn't have to remain a command-line application.
It can become infrastructure that other tools build on.
Why not just use a larger framework?
This is probably the most reasonable question.
There are excellent frameworks already.
If you're building a dashboard, a social network, an ecommerce application, or a complex interactive frontend, JPROT is probably not what you need.
And that is okay.
JPROT isn't trying to replace everything.
It is designed around a narrower philosophy:
If your website is primarily content, don't make the content pay the complexity tax of an application framework.
You should be able to understand your project.
You should be able to open the content directory and immediately know where the pages are.
You should be able to change the design without rebuilding the entire architecture.
You should be able to extend the system without becoming dependent on its internals.
And you should be able to publish the result as static HTML when you want to.
I built my own website with it
There is one test I care about more than a feature list:
Can I actually use the thing I built?
My own portfolio is built with JPROT.
That matters to me because JPROT isn't only an experiment sitting in a repository.
It is being used to build a real website.
And the project has grown through that experience.
Every limitation becomes visible when you use your own tool.
Every awkward workflow becomes impossible to ignore.
That is how JPROT has gradually moved from a small portfolio generator toward a broader content-driven website system.
Where JPROT is going
JPROT is still young.
The current release is 0.8.1.
That number is intentional.
There is still room to change the architecture.
There are still APIs to refine.
There are still ideas to test.
But the direction is becoming clear.
I don't want JPROT to be another framework that wins by accumulating features.
I want it to remain understandable.
A system where:
Markdown
↓
Content Graph
↓
Components
↓
Routes
↓
SSR / SPA navigation
↓
Static export
is enough to build serious content-driven websites.
Simple on the surface.
Extensible underneath.
Try it
Install it globally:
npm install -g jprot
or run it directly:
npx jprot
Create a project:
npm create jprot@latest my-site
cd my-site
npm start
Then open:
http://127.0.0.1:4114
Add a Markdown file.
Change the CSS.
Replace a component.
Add a plugin.
Run the linter.
Export the site.
And see what happens when a website doesn't begin with a build pipeline.
It begins with a file.
JPROT
Write Markdown. Customize everything. No build step.
GitHub: https://github.com/Moaaz-i/JPROT
Documentation: https://moaaz-i.github.io/JPROT/
Example: https://moaaz-i.vercel.app/
The project is MIT licensed and built for Node.js 18+.
Top comments (0)