Rendering a Markdown README So It Looks Right on GitHub and GitLab
· 5 min read
GitHub and GitLab share GFM but differ on relative links, embedded HTML, and tables. The three portability problems and how to check for each.

A README that looks fine in one renderer and broken in another is almost always a dialect problem, not a syntax mistake. GitHub and GitLab both render Markdown, both support GitHub Flavored Markdown, and both disagree about enough edge cases that a document can pass review on one and fail on the other.
The overlap is large enough that most READMEs never notice. The gap shows up in three specific places: relative links, which resolve against different repository roots; HTML inside Markdown, which each platform sanitizes with a different allowlist; and tables, where cell wrapping behaves differently. Knowing those three is enough to make a README portable.
Key Takeaways
- GitHub and GitLab both implement GFM, so the core syntax is interchangeable.
- Relative image and link paths resolve differently: a subdirectory README is the usual culprit.
- Both platforms sanitize embedded HTML with their own allowlist, and they differ.
- A README rendered before you publish it catches all three problems in seconds.
What Does Rendering a README Involve?
Rendering a README means taking a Markdown file in a repository and producing the page a visitor sees when they open it. No build step is involved on either platform. The file is read, parsed as GitHub Flavored Markdown, sanitized, and wrapped in the site's chrome.
That last step is where the platform differences live, and it is also why a README can be valid Markdown and still render badly. The GFM specification defines the syntax both platforms implement, and Daring Fireball's original Markdown page records the design the syntax grew from. Because a README can contain raw HTML, and because that HTML is rendered in the site's own origin, both platforms run a sanitizer over the result. Neither allows a <script> tag, and each allows a slightly different subset of everything else.
The GitHub documentation on Markdown covers the syntax GitHub accepts, and GitLab's documentation does the same for GitLab. Neither document is exhaustive about sanitizer differences, which is why the practical approach is to render locally.
Relative links are the most common failure
A README at the root of a repository and a README inside a docs/ folder resolve relative links differently, because the base path is different in each case.

At the repository root, that resolves to /owner/repo/blob/main/images/arch.png. From docs/README.md, the same line resolves one level deeper, and on GitHub the result is frequently a 404 because the blob path gains an extra segment.
Both platforms document their own link handling, GitHub through its advanced formatting documentation and GitLab through its Markdown reference, though neither states the cross-platform behaviour explicitly. Two habits prevent the problem. Use root-relative paths beginning with a slash, such as /images/arch.png, which resolve from the repository root regardless of where the file sits. Or use absolute raw URLs, which bypass the problem entirely at the cost of not working offline or in a fork.
The CommonMark reference definitions offer another route, since a definition can point at a path written once. The same applies to links between documents. A cross-reference to ../CONTRIBUTING.md works from a subdirectory and fails from the root, which is the mirror image of the image case.
Embedded HTML is sanitized differently
Both platforms permit HTML inside Markdown, because READMEs legitimately need things like a centered badge or a <picture> element for responsive images. Both also sanitize it, and their allowlists are not identical.
A <details> block for collapsible content generally works on both. A <summary> inside it, less reliably. Inline <img width="200"> sizing attributes work on GitHub and are stripped in some GitLab configurations. This is not documented as a supported feature anywhere, which means it can change between releases without notice.
The safe approach is to treat embedded HTML as a last resort. CommonMark covers badges, images, tables, and emphasis without any raw HTML, and the CommonMark specification defines the portable subset. When HTML is genuinely needed, check it in both renderers rather than assuming.
A related trap is a raw HTML block placed immediately before a Markdown block with no blank line between them. Depending on the parser's block detection, the Markdown that follows can be swallowed into the HTML block and render as literal text. The fix is a blank line.
Tables and the wrapping difference
Tables are a GFM extension, so both platforms support them, and both handle the basic case identically. The difference appears when a cell is long enough to want wrapping, which is the same limitation covered in the Markdown tables guide.
Neither platform wraps a cell across source lines, so a long value must stay on one line or the row splits into two. Beyond that, the two differ in how they size a column wider than its content, and in whether they honour the alignment colons. Both render a simple pipe table correctly, so this only becomes a problem in tables with very long cells.
Render before you publish
The practical fix for all three problems is to render the file locally before pushing, which takes seconds and catches what review does not.
The Markdown to HTML converter renders and sanitizes in the browser, using the same parse-then-sanitize order the platforms use. Paste the README in and you see what the structure will look like, including the table and the headings.
For links specifically, rendering does not help, because a local render has no repository context to resolve a relative path against. That check needs a different tool: a link checker such as Lychee will request each URL and report the ones that fail, which catches both the relative-path mistake and the link that pointed at a deleted file. Running it in CI is covered in linting Markdown in CI.
A portability checklist
Before publishing a README that needs to work in both places, four checks cover nearly everything.
- Search for relative image paths and convert any that start with
./to a root-relative/path. - Look for raw HTML blocks and confirm each one renders in both places, or replace it with Markdown.
- Check tables for cells long enough that you were tempted to wrap them.
- Run a link checker, because rendering will not catch a path that resolves to a 404.
None of these is expensive, and the first two catch nearly every real portability bug. The reason they get missed is that a README usually starts life in one repository and is only ever read on one platform, so nothing surfaces the difference until someone opens it elsewhere.
Related tools and further reading
The Markdown syntax cheat sheet covers the constructs both platforms share, and marks which are GFM extensions rather than base CommonMark. The Markdown Linter checks a README for the structural problems that survive a successful render. For the dialect boundary that decides what renders at all, CommonMark vs GitHub Flavored Markdown covers which side of the line a renderer falls on.
Written by
Abhay Khant
Abhay Khant is the founder of ToolSura, a privacy-first developer tools platform. Writes about client-side architecture, AI tooling, and the open web.