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-remoteimport { 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.
Drop a README into a React app and the pictures die.

[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 |
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.
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.mdjavascript:, 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 }.
<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";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.
npm install
npm run demoLocal fixture (relative image + link) and a live GitHub README preset.
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.
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" />
}
ISC