CLI tool
markserv/markserv avatar
markserv/markserv

Markserv serves a folder of Markdown as GitHub styled HTML with hot reload over WebSocket

๐Ÿ serve markdown as html (GitHub style), index directories, live-reload as you edit

632 stars93 forksCSSMIT

At a glance

What is it?
Markserv is a Node command line tool that renders Markdown as GitHub style HTML, indexes directories, and reloads the page as you edit. It has no browser plugin requirement, four built in themes, and a templating mode that includes nested Markdown, HTML and LESS, which is also the part that needs care.
Who is it for?
Markserv fits someone who wants to read a directory of Markdown in a browser with GitHub styling, syntax highlighting and instant updates, and who is comfortable running a local server that serves their files. It does not fit a static site that has to be published to a host, and it does not fit untrusted authors, because the templating mode can include arbitrary files from the served tree.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly CSS, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A local server for reading Markdown the way GitHub renders it

Markserv serves Markdown as HTML in GitHub style, indexes directories, and hot reloads as you edit. The project description puts it plainly, and the project page is at f1lt3r.io.

That is a narrow, specific job, and the feature list stays inside it. Markdown content rendered as HTML. GitHub flavor CSS and syntax highlighting. Directory indexes. MIME type file support. And a Just in Time Templating mode covering Markdown, HTML and LESS.

What the GitHub flavor means in practice is that the output looks like a GitHub README, with the same stylesheet, the same heading anchors, the same table rendering, and syntax highlighting for fenced code blocks. The project also documents support for MathJax, Chinese characters, a table of contents, tables, and heading anchors, with a test file for each in the tests directory.

The hot reload is the piece that turns this from a converter into a working environment. Markserv includes built-in hot-reload via WebSocket, so when you save a Markdown file the page content updates instantly without a full page reload and without any browser plugin. That last clause is the differentiator. A conventional live reload workflow usually needs a browser extension that injects a script into the page, which means the extension is installed on every machine you read docs from and the workflow breaks the moment someone opens the file in a browser where the extension is missing. Markserv pushes the update from the server side instead.

The primary language is listed as CSS, which is a quirk of the repository, but the implementation is Node, with the package published on npm as `markserv` and a CLI declared in the package manifest.

Two binaries, and the readme command that finds the nearest README

The package manifest declares two executables, not one. The `markserv` binary points at lib/cli.js and the `readme` binary points at lib/readme.js.

That second binary is the useful one for the common case. The usage examples show three ways to start it from the CLI:

shell
# Open closest README.md
$ readme

# Open file
$ markserv README.md

# Open a directory
$ markserv node_modules

`readme` with no arguments opens the closest README.md file in the browser. That is the shortcut for the situation a developer is in most often: you are in a repository, you want to read its documentation, and you do not want to remember where the file is or type a path.

The same three modes are shown again with explicit paths, so the flags are the same either way:

shell
# File
$ markserv ./path/to/file.md

# Directory
$ markserv ./

Passing a directory gives you the directory index rather than a rendered file, which is how you browse a tree of notes.

The install itself is a global npm or yarn install:

shell
# NPM
$ npm i -g markserv

# Yarn
$ yarn global add markserv

The manifest sets preferGlobal to true, and the files list is explicit about what ships: the CLI, the CLI definitions, a CLI help text file, the readme entry point, the server, the splash screen, an icons directory, and a templates directory holding the directory listing, markdown and error pages along with the GitHub stylesheets in LESS and CSS form for the dark, light and synthwave variants.

The author is Alistair MacDonald, the licence is MIT, and the homepage is f1lt3r.io.

Four themes, a --theme flag, and a browser toggle that survives reloads

Markserv ships with four themes: dark as the default, light, synthwave, and solarized. The CLI has a dedicated flag for each of the first two of the non-default ones and a general one for the rest:

shell
# Use light theme
markserv --light README.md

# Use synthwave theme
markserv --theme solarized README.md

The second block in the project text shows the same idea with the two flags spelled out, including a --synthwave example, so the pattern is a per theme flag plus a --theme flag that takes any of the four names.

The more interesting half is the browser side. You can also toggle between themes in the browser using the theme button in the bottom-right corner, and your choice is saved in localStorage and persists across hot-reloads.

That last detail is the one that makes the feature coherent. A server-side theme flag is fixed for the life of the process, so a second reader of the same served directory has to use the same theme, and a preference cannot be personal. Storing the choice in the browser and keeping it across hot-reloads means the reader picks once and keeps it, and the server flag becomes the default rather than the rule.

It also means the theme buttons need the WebSocket channel to be alive to survive a reload, since localStorage is written from the client. If you disable hot reload with --no-hotreload, the persistence story is reduced to whatever the browser keeps for the page, which is a subtle difference between the two modes that the project text does not spell out.

Just in time templating includes nested files, and that is the sharp edge

The templating mode lets you include nested content, and the project text is explicit about two things: templating is disabled by default and you enable it with --templates, and the maxDepth of includes is set to 10.

The mechanism is a small set of directives inserted into a page. Where foo/bar/baz/qux.md holds a heading, this Markdown:

markdown
# Include Markdown
{markdown: foo/bar/baz/qux.md}

produces a server response of:

html
<h1>Include Markdown</h1>
<h2>Foo Bar</h2>

Note what happened to the included file. The file said `## Qux`, and the response says `<h2>Foo Bar</h2>`. Any markdown files that you include will be transformed to HTML, and the heading is rendered from the included file's own content with a title derived from the path. The same pattern applies to HTML includes with the `{html: ...}` directive, where the included HTML is placed as is, and to LESS includes with the `{less: ...}` directive, where the LESS file is compiled and the result is wrapped in a style tag:

html
<style>{less: foo/bar/baz/qux.css}</style>

The result of that include is compiled CSS, with the variable resolved to a colour value.

The sharp edge is the security and the scope. The server fetches and renders the files you request, from the tree it is serving, and templates are rendered when you request them in your browser. That means anyone who can reach the server and can cause a page to be rendered can cause a file to be included, and the depth limit of 10 bounds recursion rather than restricting which files count. This is the reason the feature is off by default, and the reason it should stay off on a server bound to an external address.

For a local documentation server that is a sensible design. For anything shared, it needs thought about which paths are reachable.

Ports, addresses, and the specific case of serving from a remote machine

The flags for the network side are small and worth reading carefully, because the project text gives a specific recipe for a case that trips people up.

To list every option, there is a help command:

shell
$ markserv --help

The HTTP port is changed with a short flag:

shell
markserv -p 80

The harder case is external access. The project text explains that in some cases localhost might be the address on which the server is listening, in which case it is hard to make the site available to external networks even with the right IP. The example combines a port and an address:

shell
markserv -p 8642 -a 0.0.0.0

That runs the server on port 8642 and it can be accessed from external networks using the public IP of the machine on which the server is running.

And for a remote machine, the recipe is to keep it running in a separate thread even after you log out:

shell
nohup markserv -p 8642 -a 0.0.0.0 &

The project text says this makes the server instance persistent and available to access even when you log out or even when your ssh session closes, which is the case of someone setting up a markserv server on a remote machine over ssh.

The security note is implied rather than stated, and it is worth stating for yourself. Binding to 0.0.0.0 with templating enabled means that a file inclusion directive in a page can pull in any file the server can read under the served tree, and if the served tree is your home directory or a repository with secrets, that is a real exposure. The tool has no authentication, and nothing in the flags list suggests one.

The npm package, the test runner, and a few signs of an older toolchain

The package manifest is worth reading for what it reveals about the project's age and its habits.

The test setup is ava with nyc for coverage. The test script runs the coverage script, and the coverage script runs nyc with lcov, html and text reporters over ava. A husky configuration sets a pre-commit hook that runs npm test, so the test suite runs before any commit lands. The repository carries a .travis.yml and a coverage badge, and there is a .snyk file with a known vulnerabilities badge, so dependency scanning is part of the routine.

The dependencies start with bluebird, chalk, commander and connect. Commander is the CLI parser, connect is the middleware stack for the HTTP server, and chalk does the coloured terminal output for the splash screen. The keyword list in the manifest names the underlying libraries directly, including markdown-it and marked for Markdown parsing, mermaid, mathjax, and gfm for GitHub flavored Markdown support.

Two things suggest a codebase that has not been fully modernised. The chalk dependency is at version 3, and the tests run on ava with nyc rather than a newer runner. The version in the manifest is 1.21.1, and the recent tagged releases are v1.20.2 and v1.21.0, both on 2026-10-06, with the last push on 2026-10-13. So the project is being worked on, with a patch level release pattern and a release that landed two versions in one day.

For an adopter the practical consequence is that this is a tool with a stable public interface, a `readme` command and a set of flags, and a project that still maintains its own compatibility with the Node versions of the last few years.

Where Markserv is the wrong tool, and how it differs from a static site generator

Three boundaries are clear from what the project describes.

The first is that Markserv is a server, not a site generator. There is no build step that emits a directory of static HTML, no output directory, and no deploy target. The output exists only while the process runs, which is exactly right for reading notes on your own machine and exactly wrong for publishing a documentation site that has to survive a reboot and be served by a CDN.

The second is that the templating mode executes. Including a file is a server side action that reads from disk and renders, with a recursion depth of 10 and no allowlist. On a machine where the served directory is trusted, that is a feature. On a shared server, it is an arbitrary file read with a depth limit.

The third is the feature set. There is no search across the served tree, no versioning, no per page metadata, no front matter, and no plugin system described anywhere. The GitHub flavor is about rendering, not about GitHub's collaboration features.

The comparison with a static site generator comes down to the output. A generator reads Markdown once, writes HTML files, and you host them anywhere, including on a static host, with no server process to keep alive and no attack surface at all. Markserv keeps a Node process running, serves the files with GitHub styling that matches what you see on the site, and updates the browser as you type. If your need is reading and writing documentation with a live preview, a generator plus a separate live reload server is more machinery than you need, and Markserv is the smaller arrangement. If your need is publishing, the generator wins because its output outlives the process.

Editorial conclusion

Markserv fits someone who wants to read a directory of Markdown in a browser with GitHub styling, syntax highlighting and instant updates, and who is comfortable running a local server that serves their files. It does not fit a static site that has to be published to a host, and it does not fit untrusted authors, because the templating mode can include arbitrary files from the served tree. Before enabling --templates, decide which directories are reachable from the served path, and if you bind the server to an external address, remember that -a 0.0.0.0 exposes whatever is under that path to the network.

Frequently asked questions

How do I install Markserv?

Install it globally with npm i -g markserv, or with yarn global add markserv. The package registers two commands, markserv and readme.

What does the readme command do in Markserv?

Running readme with no arguments opens the closest README.md file in the browser. You can also start the server with an explicit path to a file or a directory, for example markserv README.md or markserv ./.

How does Markserv hot reload work, and can I turn it off?

Hot reload is built in via WebSocket, so saving a Markdown file updates the page content instantly without a full page reload and without any browser plugin. You can disable it with the --no-hotreload flag.

How do I include other files in a Markserv page?

Enable templating with --templates and use a directive such as {markdown: path/to/file.md}, {html: path/to/file.html} or {less: path/to/file.css}. Included Markdown is transformed to HTML, included LESS is compiled to CSS, and the maximum include depth is 10.

How do I serve Markserv on a port and expose it to a network?

Use -p for the port and -a for the address, for example markserv -p 8642 -a 0.0.0.0, and prefix the command with nohup and a trailing ampersand if you want it to keep running after you log out of an ssh session.

Official sources

  1. License: MIT
  2. markserv/markserv on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/markserv-markserv.svg)](https://hysenlabs.com/projects/markserv-markserv)