Render Markdown from a string in Svelte (mdsvex / SvelteKit)
Tags: svelte, sveltekit, mdsvex, markdown
Published: 2022-09-03 16:08:49

The Svelte Markdown tutorials I've seen out there assume that you have access to a `.md` file or are writing Markdown directly in a preprocessed file.

What if you wanted to render a raw **string** that **contains** Markdown? Example use-case: You want the flexibility of being able to serve Markdown content from a CMS (such as [Strapi](https://strapi.io/)) without having to make changes to a `.md` file.

There are a few ways you can achieve this, in this solution we'll be using `mdsvex` to convert the Markdown into HTML format, then rendering that HTML. `mdsvex` is a Markdown preprocessor which is essentially a wrapper for [MDX](https://mdxjs.com/), making it compatible for Svelte.


**Installation**

If you're not using [SvelteKit](https://kit.svelte.dev/), check out the [MDsveX docs](https://mdsvex.com/) for alternative installations, if you _are_:
 
Start by installing it as a dev-dependency.

```sh
npm i --save-dev mdsvex
```
or...
```sh
yarn add --dev mdsvex
```

Then update the Svelte configuration to include an `extensions` array:

(We'll just be using a `.svelte` file, but you can also include extensions such as `.svx` if you plan on using those file types)

_`svelte.config.js`_

```ts
export default {
  preprocess: [
    // your existing config
  ],

  extensions: ['.svelte'], // Add this
 
  kit: {
    // your existing config...
  }
};
```

That's all the configuration we need for now. ☑️


**Passing the compiled string to the component**

Let's set up a route loader that simulates fetching data from an API:

_`+page.server.ts`_
```ts
import type { Load } from '@sveltejs/kit';

const MOCK_RESPONSE_FROM_API = `
## Lorem

Lorem is currently extended with the following plugins.
Instructions on how to use them in your application are linked below.

| Plugin | README |
| ------ | ------ |
| Dropbox | [plugins/dropbox/README.md](Link) |
| Medium | [plugins/medium/README.md](Link) |
| Google Analytics | [plugins/googleanalytics/README.md](Link) |
`;

export const load: Load = async () => {
	const response = MOCK_RESPONSE_FROM_API; // Get data with eg. `fetch`

	return response;
};
```

Next, we can import the `compile` function from `mdsvex`, declare a variable passing the (mock) `response` into it, and return it so it's available in our component. We'll also log it to see what gets returned from `compile`:

_`+page.server.ts`_

```ts
import { compile } from 'mdsvex';
// ...
const compiledResponse = await compile(response);

console.log('compiledResponse is: ', compiledResponse);

return compiledResponse;
// ...
```

_console output_ (Given this is being run server side, it's viewable on the terminal, not the browser console)

![Console output from compile](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2kax8v10idjg6eyqqlb0.png)

As you can see, the Markdown has been compiled into what looks like HTML string format, appended together with the `+` operator, under the key of `code`.

So we can update the return statement to only include what we need:

_`+page.server.ts`_

```ts
export const load: Load = async () => {
	const res = MOCK_RESPONSE_FROM_API; // Get data with eg. `fetch`
	const compiledResponse = await compile(res);

	return { content: compiledResponse?.code };
};
```


**Rendering the HTML directly into the component**

Now from our component, all we need to do is receive the `data` through `export let`, and render it.

Usually, strings are inserted as plain text, meaning that characters like < and > have no special meaning. So given we've now got HTML that we want to render directly, we can use [HTML tags](https://svelte.dev/tutorial/html-tags):

```html
<p>{@html string}</p>
```

so in our case...

_`+page.svelte`_

```html
<script lang="ts">
	export let data = {
		content: ''
	};
</script>

<div>{@html data.content}</div>
```

Which gives us our rendered Markdown 🪄:

![Rendered Markdown](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r16v3esotlwz7nay6sio.png)

Note from the docs:

> Svelte does not sanitise expressions before injecting HTML. If the data comes from an untrusted source, you must sanitise it, or you are exposing your users to an XSS vulnerability.


**Further Customisation**

The `compile` method accepts a second parameter, `options`:

```ts
interface MdsvexOptions {
	extensions: string[];
	smartypants: boolean | smartypantsOptions;
	layout: string | { [name: string]: string };
	remarkPlugins: Array<plugin> | Array<[plugin, plugin_options]>;
	rehypePlugins: Array<plugin> | Array<[plugin, plugin_options]>;
	highlight: { highlighter: Function, alias: { [alias]: lang } };
	frontmatter: { parse: Function; marker: string };
}
```

This means that you can transform your HTML with tools like [rehype](https://github.com/rehypejs/rehype) or [remark](https://github.com/remarkjs/remark-html). You can find out more about the available options [here](https://mdsvex.com/docs#options).

That's it! 👨‍💻

If you need any further information on the above or anything else related - check out the [SvelteKit docs](https://kit.svelte.dev/docs/introduction), the [MDX docs](https://mdxjs.com/) or the official [Svelte discord](svelte.div/chat).