DEV Community

Moaaz Yahia
Moaaz Yahia

Posted on

What If Building a Website Didn't Need a Build Step?

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
Enter fullscreen mode Exit fullscreen mode

You write:

---
title: "My Project"
description: "A short description"
---

## My Project

This is my project.
Enter fullscreen mode Exit fullscreen mode

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' },
  ],
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then:

cd my-site
npm start
Enter fullscreen mode Exit fullscreen mode

Your development server starts at:

http://127.0.0.1:4114
Enter fullscreen mode Exit fullscreen mode

You can also initialize an existing directory:

jprot init --portfolio
Enter fullscreen mode Exit fullscreen mode

or:

jprot init --docs
Enter fullscreen mode Exit fullscreen mode

or:

jprot init --resume
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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>`
      )
    )
  },
}
Enter fullscreen mode Exit fullscreen mode

Then:

export default {
  plugins: ['./plugins/analytics.js'],
}
Enter fullscreen mode Exit fullscreen mode

The plugin API exposes a deliberately small surface:

addComponent
addRoute
extendMarkdown
on
config
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Write:

---
title: Test Page
---

# Hello
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

That means you can develop dynamically and publish statically.

The workflow becomes:

write
  ↓
run JPROT
  ↓
preview
  ↓
export
  ↓
deploy
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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',
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

There are also site-aware checks.

For example:

jprot lint
Enter fullscreen mode Exit fullscreen mode

can look for things such as broken links, missing metadata, and duplicate anchors.

And:

jprot check
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

For example:

const app = await createJprot({
  root: '/path/to/project',
  port: 3000,
  watch: true,
})

await app.listen(3000)
Enter fullscreen mode Exit fullscreen mode

Or export a site programmatically:

await exportSite({
  root: '/path/to/project',
  outDir: '/tmp/dist',
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

is enough to build serious content-driven websites.

Simple on the surface.

Extensible underneath.


Try it

Install it globally:

npm install -g jprot
Enter fullscreen mode Exit fullscreen mode

or run it directly:

npx jprot
Enter fullscreen mode Exit fullscreen mode

Create a project:

npm create jprot@latest my-site
cd my-site
npm start
Enter fullscreen mode Exit fullscreen mode

Then open:

http://127.0.0.1:4114
Enter fullscreen mode Exit fullscreen mode

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)