---
title: "Client and handler"
description: "Serve the Content instance over HTTP and read it from the browser with the same API."
canonical_url: "https://content.comark.dev/advanced/client-and-handler"
---
# Client and handler

> Serve the Content instance over HTTP and read it from the browser with the same API.

Server code reads the Content instance directly: import `content`, call `get()`. Browser code can't, because the instance and your files live on the server. For that case Comark Content ships two halves that speak the same API.

## When you need this

You don't, if every read happens on the server: Next.js server components, SvelteKit `load` functions, Nitro and Hono routes, Node scripts, and Nuxt server routes all call the instance in-process. Skip this page until one of these applies:

- A **client component or client-side router** fetches content after the page loads.
- **Search or navigation** runs in the browser against live content.
- A **separate front-end** talks to a content server on another origin.

A Vite single-page app is a different shape again: it has no server at runtime, so it runs the whole instance in the browser from a [snapshot](https://content.comark.dev/integrations/vite#create-the-browser-content-instance) rather than through a client.

## The pair

**[`content.handler(request)`](https://content.comark.dev/reference/content/handler)** exposes the instance on the server. It's one function from a web-standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) to a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), so it mounts on any server that hands you a `Request`.

**[`createContentClient()`](https://content.comark.dev/reference/client/create-content-client)** consumes it from the browser with the same read methods: [`get()`](https://content.comark.dev/reference/client/get), [`list()`](https://content.comark.dev/reference/client/list), and [`navigation()`](https://content.comark.dev/reference/client/navigation). Same names, same return shapes. The parser and your files stay on the server; only data crosses the wire.

Here is a complete pair in Nuxt. The other [framework guides](https://content.comark.dev/getting-started/render-your-first-page) show the same two files in their own layout.

```ts [server/utils/content.ts]
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'

export const content = comarkContent({
  source: fs('./content'),
})
```

```ts [server/api/content/[...path\\].ts]
export default defineEventHandler((event) => content.handler(toWebRequest(event)))
```

```ts [app/utils/content.ts]
import { createContentClient } from 'comark-content/client'

export const contentClient = createContentClient()
```

```vue [app/pages/[...slug\\].vue]
<script setup lang="ts">
import { MarkdownDocument } from '@comark/vue'

const route = useRoute()
const { data: page } = await useAsyncData(route.path, () => contentClient.get(route.path))
</script>

<template>
  <MarkdownDocument v-if="page" :value="page" />
</template>
```

## Where they meet

The two halves agree on one URL. The handler strips the instance's [`basePath`](https://content.comark.dev/reference/content/comark-content#options-basepath), `/api/content` by default, from the request path before dispatching; the client sends its requests to its own `basePath`, with the same default. Mount the handler at `/api/content` and create the client with no options, and they match.

If the handler lives elsewhere, set both sides:

```ts
// server
export const content = comarkContent({ source: fs('./content'), basePath: '/content-api' })

// browser
export const contentClient = createContentClient({ basePath: '/content-api' })
```

Pass `baseURL` to the client when the server runs on another origin, and `fetch` to use your framework's fetch (Nuxt's `$fetch`, for example, so server-rendered reads are reused on the client). Every option is in the [`createContentClient` reference](https://content.comark.dev/reference/client/create-content-client).

## What the handler serves

Behind the single function are a few routes, all under the base path:

| Route                                   | Serves                                                                                                                                                     |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /get/<path>`                       | One page, as `get()` returns it                                                                                                                            |
| `GET /list`                             | Every page's frontmatter, as `list()` returns it                                                                                                           |
| `GET /navigation`                       | The navigation tree                                                                                                                                        |
| `GET /manifest`, `GET /snapshot/<name>` | The index and the parsed content as [artifacts](https://content.comark.dev/advanced/artifacts-and-hydration#transport-artifacts), used by snapshot sources |
| `GET /<section>`                        | Any section a plugin registers with [`addServeHandler()`](https://content.comark.dev/reference/content/add-serve-handler)                                  |

You rarely call these URLs yourself; the client does. They're listed here so the network tab makes sense and so you know what a plugin can add.

## Extending the pair

Plugins that add a server method usually add a client method too: the [`sqlQuery`](https://content.comark.dev/plugins/built-in/sql-query) and [`sqliteFullTextSearch`](https://content.comark.dev/plugins/built-in/full-text-search) plugins each ship a client half, so `contentClient.search()` calls the server's `content.search()`.

To add your own section, write a Content plugin that registers a serve handler and a client plugin that calls it. [Custom plugins](https://content.comark.dev/plugins/custom/plugin-api) walks through both halves with a complete example.

## Several instances

A [hub](https://content.comark.dev/advanced/hub) exposes one handler for all its instances, and a client can bind to one instance by name so a colliding path comes back from the instance you meant. See [`contentHub()`](https://content.comark.dev/reference/content-hub#handler-routes) for the route table.


## Sitemap

See the full [sitemap](https://content.comark.dev/sitemap.md) for all pages.
