This section covers all NuxtPress customization options.

Custom stylesheets

If you're only using NuxtPress for Markdown pages, you can just add CSS to the default layout. Learn more about Nuxt layouts here.

For customizing the bundled apps, you can use npx nuxt-press eject:

$ npx nuxt-press eject <mode>/theme

There are four ejectable stylesheets:

$ npx nuxt-press eject docs/theme
$ npx nuxt-press eject blog/theme
$ npx nuxt-press eject slides/theme

Running any of these commands will append the specified stylesheet to the <srcDir>/ file. It will also be created automatically the first time if does not exist yet.

Beware: this command doesn't check if the stylesheet has been previously ejected, so running it multiple times will keep adding to

NuxtPress includes normalize.css and wysiwyg.css, with minimal customizations. All theme stylesheets are written using css-preset-env stage-0 features.

When detecting the presence of, NuxtPress will simply add it to the stack. To avoid duplication, consider using nuxt-purgecss. Or, you can disable all built-in NuxtPress styles altogether by setting naked to true in

  "naked": true

For convenience, all styles are kept in one theme file, so that you get everything when you eject. This is why <style> isn't used in the bundled app components.

Ejectable templates

If overriding stylesheets isn't enough for your needs, you can eject NuxtPress bundled app templates for low-level modifications.

The concept of ejecting is taken from VuePress. NuxtPress gives you the same functionality, with a little more control: if all you need is to customize a blog sidebar, you can eject that particular template (blog/sidebar) and have it shadow the built-in one. NuxtPress in this regard behaves similarly to Gatsby's component shadowing, in a more streamlined fashion.

Eject with npx:

$ npx nuxt-press eject blog/sidebar

See NuxtPress source code for bundled apps here.

Below is a list of ejectable templates for each of NuxtPress bundled apps:

Key Path
docs/topic docs/header
press/docs/components/topic.vue press/docs/components/header.vue
blog/entry blog/sidebar
press/blog/components/entry.vue press/blog/components/sidebar.vue
slides/slides slides/plugin
press/slides/components/slides.vue press/slides/plugins/slides.client.js

If you're customizing everything, running npx nuxt-press eject <mode> will eject all templates associated with the specified mode.

Every bundled app is written as a regular Nuxt app: they make use of middleware, plugins and layouts. All bundled apps depend on the common app bundle, which populates $press and $press.source.

The common app bundle also includes the main source route, which in turns loads the right components for the specified source path. You'll rarely need to edit the common app bundle templates, but they're also ejectable:

Key Path
common/source common/nuxt

This makes NuxtPress nearly fully customizable. The rest of it is really just the module code that bootstraps all bundled apps if enabled into your Nuxt app.

Using components

To use custom Vue components in your Markdown sources, just create a plugin to import and register the component globally:

import ColorPicker from '@radial-color-picker/vue-color-picker'

Vue.component('color-picker', ColorPicker)

Since NuxtPress operates under the assumption all Markdown is provided by the author (and not via third-party user submission), sources are processed in full (tags included), with a couple of caveats from rehype-raw:

  1. Can't use self-closing tags, i.e., this won't work:
<color-picker />

But this will:

  1. When placing Markdown inside a component, it must be preceded and followed by an empty line, otherwise the whole block is treated as custom HTML.

This won't work:

<div class="note">
*Markdown* and <em>HTML</em>.

But this will:

<div class="note">

*Markdown* and <em>HTML</em>.


As will this:

<span class="note">*Markdown* and <em>HTML</em>.</span>

This last example works because <span> is not a block-level tag.

Markdown loader

By default, NuxtPress loads content from the file system.

For docs mode, it will load all Markdown files in the current directory and sub directories recursively and group them in a serial list of topics.

Their final URL follows the format:


For blogs mode, Markdown files are grouped as a chronological list of entries.

Their final URL follows the format:


For slides mode, Markdown files are treated as individual slideshows.

Their final URL follows the format:


You can override Markdown loading functions using

export default {
  docs: {
    source: {
      markdown(rawSource) {
        const { data, body } = yourMarkdownProcessor(rawSource)
        return {, body }

In general, NuxtPress expects <mode>.source.markdown() to return an object with title, path and body. Depending on the mode, additional properties may be used. See the configuration object for each bundled app to learn more about them.

Generate overrides

NuxtPress will infer routes for nuxt generate based on the autogenerated Markdown source routes, but if you're using heavily customized templates that use different data sources, you must also override the default generate routes. You can do so via extendStaticRoutes(). The first parameter is a hash where route payloads can be assigned to route paths. The second parameter is a convenience function to import static source from the distribution directory.

In the example below, the payload for / is set to the blog/archive.json source file, which is automatically added to the distribution by the blog bundle.

export default {
  // Use archive payload to generate / route
  async extendStaticRoutes(routes, staticImport) {
    routes['/'] = await staticImport('blog/archive.json')

On the client, source files are downloaded on-demand via fetch() before each route is rendered, except for the first route which comes prerendered from the server.

Server middleware

You can disable filesystem loading altogether by providing your own custom API handlers for retrieving indexes and Markdown source files.

API methodConfiguration key

Note however that when overriding API handlers, all API handlers must be provided. If your using NuxtPress docs bundled app, you can't for instance override docs.api.index and not also override common.api.source.

For overriding API handlers, use

import mdProcessor from 'your-own-markdown-preprocessor'

const dummyEntry = {
  path: '/this-is/used-as-a-route',
  title: 'hello world',
  body: mdProcessor('# hello world\n\n`)

export default {
  common: {
    api: {
      source(source, req, res, next) {
        if (source === 'this-is/used-as-a-route') {
  docs: {
    api: {
      index(req, res, next) {

The example uses very simple logic for clarity. Note that the signature for the source() handler has 4 parameters, the first being the source path. This is for your convenience, because NuxtPress expects a API request matching the URL accessed in your Nuxt app:

URL accessedAPI request

In other words, if a HTTP request fails to be captured by Nuxt's route handlers, it will land at a final source handler that tries to retrieve it from the API. If you don't provide your own API handlers, NuxtPress uses built-in API handlers that deliver the pregenerated files from the default filesystem loader.