Simple file-based website engine. Minimal config, maximum pleasure.
Mikrob is a zero-configuration website engine that transforms your file structure into a fully functional website. Built on top of Bun and Hono, it offers a flexible page system with JSX views.
Install the package with your favourite package manager:
bun install mikrobCreate index.ts in root of your project and paste:
import { mikrob } from 'mikrob'
await mikrob()Then start the server:
bun index.tsWhen opened in the browser, it will automatically detect and serve corresponding page:
- /pages/index.ts → localhost:3000,
- /pages/about.ts → localhost:3000/about.
Mikrob also supports watch mode for automatic reloading:
bun --watch index.tsNote
When you use Bun's --watch flag, Mikrob automatically detects it and sets up file watching to reload pages and views when they change. This is useful when you want to keep the app running continuously while modifying content, like adding blog posts or updating pages.
Mikrob uses a file-based routing system similar to Next.js and Astro. Pages are automatically rendered from files in your pages directory, supporting multiple formats:
- JSON - For static content,
- TS/TSX - For dynamic content and data fetching,
- Markdown - For content-rich pages and blog posts.
index.{json,ts,tsx,md} files receive special treatment by stripping away the "index" segment from the path.
| File | Address |
|---|---|
| /pages/blog/hello-world.json | /blog/hello-world |
| /pages/blog/hello-world/index.ts | /blog/hello-world |
| /pages/blog/index.tsx | /blog |
| /pages/blog/index/index.md | /blog |
When Mikrob finds the file for a requested path, it will read its contents and pass this data to the defined view template.
Page data is accessible in views as a prop passed to the component (more on that in the Views section). If the page file contains the description property, it will be accessible as props.page.description.
You can use three file types to define pages:
{
"view": "templates/Product.tsx",
"title": "Vacuum-o-matic 2000",
"description": "Lorem ipsum dolor zamęt.",
"tags": ["hobby", "travel", "music"]
}This type is useful for making some calculations or data fetching before returning the data to view. JS/JSX files can also be used.
const posts = [ … ]
export default {
view: 'Index.tsx',
title: 'Hello world!',
date: '2019-03-07',
posts,
}Each *.md file should consist of two parts: the front matter (in JSON format) and the page body written in Markdown. This structure is designed for content-heavy pages and blog posts. The Markdown section will be accessible in view as props.page.body.
---
{
"view": "post",
"title": "My first post",
"published": "2022-10-20"
}
---
This is the content of my **first post**. It's nice.Important
Mikrob doesn't automatically handle Markdown parsing. This gives you the flexibility to customize how Markdown is processed, like adding syntax highlighting or sanitizing it before display. To keep things simple, Mikrob only provides raw Markdown content to work with. If you need a tool to parse it, check out Marked.
Note
Mikrob uses JSON instead of YAML for the front matter to reduce the number of dependencies required by the package. YAML needs an additional package for reading, while JSON is natively supported (duh).
Pages are loaded in alphabetical order based on their path and file name.
To customize the order, prefix the directory and file names with a number or timestamp. For example:
pages/
└── blog/
├── 01-uno.md
└── 02-dos.md
This prefix becomes part of the URL: blog/01-uno.md would be accessible as /blog/01-uno.
To keep the URLs clean, you can overwrite the autogenerated path using the path property.
{
"path": "/blog/uno"
}Mikrob uses Hono under the hood, so any path format supported by Hono is possible. You can use named parameters, regex, wildcards. See Hono documentation for more details.
As an example, you can configure a path parameter for news pagination like this:
{
"view": "templates/news",
"path": "/news/:page{[0-9]+}"
}In this example, the numerical page information from the URL is captured and assigned to the page parameter. When you open the /news/2 URL, the "2" will be accessible through params.context.req.param().page.
You can create a single page to respond to multiple paths.
The "not found" error page is a good example. To handle all requests for non-existent pages with a custom 404 error page, create a JSON file at /pages/404.json with the following contents:
{
"view": "templates/404",
"path": "*",
}Sometimes, you may want to inform web browsers or search engines about a page's specific status.
For example, when showing a "not found" page, you can send a 404 status code by setting the status property to 404. This action will automatically transmit the status code to the browser.
{
"view": "templates/404",
"path": "*",
"status": 404,
}You can create a page that redirects to another location by using the redirect property. This property specifies the URL to which you want to redirect. You can use it alongside the status property to create a permanent redirect that, for example, returns a 301 HTTP code.
{
"redirect": "http://domain.com",
"status": 301
}Mikrob automatically serves static files from the static directory using Bun's built-in static file serving. Any files placed in this directory (images, CSS, JavaScript, fonts, etc.) are served directly without processing.
For example:
- /static/style.css → localhost:3000/style.css
- /static/images/logo.png → localhost:3000/images/logo.png
You can customize the static directory location by passing the staticDir option to the mikrob() function:
await mikrob({
staticDir: 'public'
})Mikrob leverages JSX components as view templates, providing a familiar and powerful way to structure the UI. Views are stored in the views directory and can access page data through props.
View Requirements:
- File Type: Must be JavaScript/TypeScript (.js, .jsx, .ts, or .tsx),
- Default Export: Must have a single default export,
- Component Type: Must export a function component.
Each view component receives a standardized set of props through the PageView type:
context: Hono's Context object,pages: Array of all registered pages,page: Current page data with its properties.
Example:
{
"view": "templates/Post.tsx",
"title": "Hello",
"content": "Lorem ipsum…",
}Corresponding view component:
import type { PageView } from 'mikrob'
const Post: PageView = ({ context, pages, page }) => {
return (
<article>
<h1>{page.title}</h1>
<div>{page.content}</div>
</article>
)
}
export default PostPage views can also directly return a Response object for full control over the HTTP response. This enables:
- Custom status codes,
- Conditional redirects,
- Custom headers,
- Alternative content types.
Example:
import type { PageView } from './types'
const SecretPage: PageView = ({ context }) => {
const isAuthorized = checkAuth(request)
if (!isAuthorized) {
return new Response('Unauthorized', { status: 401 })
}
return <div>Secret Content</div>
}
export default SecretPage