Skip to content

Repository files navigation

react-markdown-remote

npm types license

Point src at a markdown file. Relative images and links actually work.

react-markdown takes a string. This package takes a URL. It fetches the file, rewrites ./shot.png and ./api.md against that file — not against the page you dropped the component on — and renders React elements. GitHub blob URLs, GitLab files, gists, and any http(s) markdown document.

No dangerouslySetInnerHTML. No jQuery. GitHub-flavored markdown is on by default.

Using an LLM or coding agent? Copy the agent spec into the prompt.

npm install react-markdown-remote
import { RemoteMarkdown } from "react-markdown-remote";
import "react-markdown-remote/styles.css"; // optional typography

<RemoteMarkdown src="https://github.com/acme/app/blob/main/docs/guide.md" />

Peer: react >= 18. Dual ESM/CJS. "use client" for Next.js App Router.

npm · GitHub

The problem this solves

Drop a README into a React app and the pictures die.

![diagram](./images/flow.png)
[API](./api.md)

On GitHub those paths are relative to the markdown file. In your app the browser resolves them against the current page, so they 404. Blob URLs cannot be used as img src either — images need raw.githubusercontent.com, links need github.com/.../blob/....

That glue is the whole product. Parsing is react-markdown. Fetch + rewrite is this package.

You have Use
A markdown string already in memory react-markdown
A file at a URL, with ./ images and links react-markdown-remote
JSX inside markdown MDX

What src accepts

src Fetches ./shot.png becomes
https://github.com/org/repo/blob/main/docs/guide.md raw GitHub raw image under docs/
https://github.com/org/repo README.md on HEAD raw image at repo root
https://github.com/org/repo/tree/main/docs docs/README.md raw image under docs/
https://gitlab.com/group/app/-/blob/main/README.md GitLab /-/raw/ GitLab raw image
https://gist.github.com/user/id gist raw gist raw
https://example.com/docs/guide.md that URL https://example.com/docs/shot.png
/docs/README.md same origin same-origin, relative to the file

On GitHub and GitLab, /root.png is the repo root, matching how those hosts render READMEs. On a generic URL it is the host root.

Slash-branches: .../blob/refs/heads/feature/foo/README.md.

Rewrite, without the component

import { resolveDocument, rewriteUrl } from "react-markdown-remote";

const doc = resolveDocument("https://github.com/acme/app/blob/main/docs/guide.md");
rewriteUrl("./shot.png", doc, "image");
// https://raw.githubusercontent.com/acme/app/main/docs/shot.png
rewriteUrl("./extra.md", doc, "link");
// https://github.com/acme/app/blob/main/docs/extra.md

javascript:, data:, vbscript:, and file: are dropped. Absolute http(s) links stay. GitHub blob URLs used as images become raw.

Need the string on the server? await loadMarkdown(src) returns { markdown, document }.

API

<RemoteMarkdown
  src="https://github.com/org/repo/blob/main/README.md"
  loading={<p>Loading…</p>}
  error={(err) => <p>{err.message}</p>}
  onLoad={({ markdown, document }) => console.log(document.fetchUrl)}
  components={{ a: (props) => <a {...props} target="_blank" rel="noreferrer" /> }}
/>
Prop Type Default Notes
src string required Document URL
fetch typeof fetch globalThis.fetch Proxy for hosts without CORS. Keep src as the real document URL
requestInit RequestInit Merged in; signal is owned by the component
gfm boolean true remark-gfm (tables, task lists, strikethrough)
loading ReactNode status text null hides it
error ReactNode | (Error) => ReactNode alert with the message null hides it
onLoad (result) => void Markdown + resolved document
onError (Error) => void Not called for aborted fetches
origin string window.location.href Base for relative src

Every react-markdown option except children is forwarded (components, remarkPlugins, rehypePlugins, urlTransform, …). Your urlTransform runs after the relative rewrite; defaultUrlTransform sanitizes last.

import {
  RemoteMarkdown,
  useRemoteMarkdown,
  loadMarkdown,
  resolveDocument,
  rewriteUrl,
} from "react-markdown-remote";

CORS and XSS

raw.githubusercontent.com sends Access-Control-Allow-Origin: *. Many other hosts do not. If the browser blocks the request, pass a fetch that hits your proxy, and leave src pointing at the real file so rewrite still has the right base.

Untrusted markdown is safe by default: no dangerouslySetInnerHTML, no raw HTML parsing. Do not add rehype-raw unless you sanitize.

Demo

npm install
npm run demo

Local fixture (relative image + link) and a live GitHub README preset.

Migration from react-markdown-to-html

That 2015 package dumped marked through innerHTML. It is deprecated on npm. This is the replacement.

- import Markdown2HTML from "react-markdown-to-html"
- <Markdown2HTML src="README.md" />
+ import { RemoteMarkdown } from "react-markdown-remote"
+ <RemoteMarkdown src="https://github.com/org/repo/blob/main/README.md" />

React 18+ is required. jQuery, lodash, and marked are gone. A bare README.md only works if that file is served from the current origin.

For LLMs

Copy everything in the block below into an LLM or coding-agent prompt when you want it to use react-markdown-remote.

Use react-markdown-remote v2 to fetch a markdown FILE from a URL and render it in React 18+.

Install: npm install react-markdown-remote
Import:  import { RemoteMarkdown } from "react-markdown-remote"
Optional CSS: import "react-markdown-remote/styles.css"
Peer: react >= 18. Dual ESM/CJS. The published bundle starts with "use client".
Default export is the same component.

This is NOT a markdown parser you pass a string to. react-markdown already does that.
This package's job is: fetch src + rewrite relative images/links against THAT file.

API:
  <RemoteMarkdown
    src: string                 // required. Document URL, not the markdown text
    fetch?: typeof fetch        // proxy if CORS blocks; keep src as the REAL document URL
    requestInit?: RequestInit   // merged; do not pass signal
    origin?: string             // base for relative src (default window.location.href)
    gfm?: boolean               // default true (remark-gfm)
    loading?: ReactNode         // default "Loading markdown…"; null to hide
    error?: ReactNode | ((err: Error) => ReactNode)  // default alert; null to hide
    onLoad?: (result: { markdown: string, document: ResolvedDocument }) => void
    onError?: (err: Error) => void   // not called on abort
    className?: string
    style?: CSSProperties
    id?: string
    ...reactMarkdownOptions     // components, remarkPlugins, rehypePlugins, urlTransform, …
  />

  // Headless:
  useRemoteMarkdown(src, { fetch, requestInit, origin })
    -> { status: "loading" }
     | { status: "ready", markdown, document }
     | { status: "error", error: Error }

  await loadMarkdown(src, { fetch, requestInit, signal, origin })
    -> { markdown, document }

  resolveDocument(src, origin?) -> ResolvedDocument
  rewriteUrl(url, document, "image" | "link") -> string

src mapping:
- github.com/org/repo/blob/ref/path.md  -> fetch raw.githubusercontent.com/.../path.md
  images: raw under the file's directory
  links:  github.com/.../blob/... under the file's directory
- github.com/org/repo                   -> README.md on HEAD
- github.com/org/repo/tree/ref/dir      -> dir/README.md
- gitlab.com/group/app/-/blob/ref/file  -> /-/raw/ for fetch and images, /-/blob/ for links
- gist.github.com/user/id               -> gist.githubusercontent.com/user/id/raw
- https://example.com/docs/guide.md     -> that URL; relatives against /docs/
- /docs/README.md                       -> same origin (needs a browser origin or origin option)
- Slash branches: .../blob/refs/heads/feature/foo/README.md
- On GitHub/GitLab, /foo.png is REPO root. On generic URLs, /foo.png is HOST root.

Rules:
- Do NOT use dangerouslySetInnerHTML. Do NOT add rehype-raw unless the caller sanitizes.
- Do NOT pass the markdown string as children. Pass src.
- If you proxy fetch, src must still be the original GitHub/GitLab/file URL so rewrite works.
- javascript:, data:, vbscript:, file: URLs are stripped. Leave that alone.
- User urlTransform runs AFTER rewrite; defaultUrlTransform sanitizes last.
- Do not fetch on every render. The component aborts in-flight work when src changes.
- Successor to the deprecated package react-markdown-to-html (jQuery + marked + innerHTML). Never revive that API.

Minimal:

  import { RemoteMarkdown } from "react-markdown-remote"

  export function Readme() {
    return <RemoteMarkdown src="https://github.com/acme/app/blob/main/README.md" />
  }

License

ISC

About

Fetch a markdown file from a URL and render it in React. Relative images and links resolve against the document, not the page. GitHub and GitLab blob URLs just work.

Topics

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages