diff --git a/pygitweb/main.py b/pygitweb/main.py
index e8e261d..25bb7a8 100644
--- a/pygitweb/main.py
+++ b/pygitweb/main.py
@@ -106,6 +106,16 @@ env = Environment(
 PREAMBLE = env.get_template("preamble.html")
 POSTAMBLE = """</div></div></div><script src="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/js/tabler.min.js"></script></body></html>"""
 
+# Map file extension to highlight.js language (class name)
+BLOB_LANG = {
+    "py": "python", "js": "javascript", "ts": "typescript", "jsx": "javascript", "tsx": "typescript",
+    "html": "html", "htm": "html", "css": "css", "scss": "scss", "json": "json", "md": "markdown",
+    "sh": "bash", "bash": "bash", "yml": "yaml", "yaml": "yaml", "xml": "xml", "go": "go",
+    "rs": "rust", "java": "java", "c": "c", "h": "c", "cpp": "cpp", "cc": "cpp", "cxx": "cpp",
+    "sql": "sql", "r": "r", "rb": "ruby", "php": "php", "swift": "swift", "kt": "kotlin",
+    "vue": "xml", "toml": "toml", "ini": "ini", "cfg": "ini", "dockerfile": "dockerfile",
+}
+
 # Todo handle with nginx route
 _static_dir = Path(__file__).parent / "static"
 if _static_dir.is_dir():
@@ -595,12 +605,37 @@ def git_blob(project: str, h: str | None, f: str | None, *, raw: bool = False) -
             except UnicodeDecodeError:
                 media_type = "application/octet-stream"
         return Response(content=data, media_type=media_type)
-    # HTML view: raw content, escaped for safe display
+    # HTML view: raw content, escaped for safe display (so HTML/XML/SVG are not parsed as DOM)
     text = to_utf8(data) or ""
-    body = sanitize(text) or ""
+    body = esc_html(sanitize(text) or "") or ""
+    lines = text.split("\n")
+    num_lines = max(1, len(lines))
+    line_nums = "\n".join(str(i) for i in range(1, num_lines + 1))
+    ext = os.path.splitext(f.split("/")[-1])[1].lstrip(".").lower()
+    lang = BLOB_LANG.get(ext, "")
+    lang_attr = f" language-{lang}" if lang else ""
+    blob_html = (
+        f"<div class=\"blob-view\">"
+        f"<div class=\"blob-line-nums\" aria-hidden=\"true\">{esc_html(line_nums)}</div>"
+        f"<pre class=\"blob-content\"><code class=\"hljs{lang_attr}\">{body}</code></pre>"
+        f"</div>"
+    )
+    blob_script = """
+<script>
+(function(){
+  var el = document.getElementById('Highlight-script');
+  function run() {
+    var code = document.querySelector('.blob-content code');
+    if (code && typeof hljs !== 'undefined') hljs.highlightElement(code);
+  }
+  if (el) {
+    if (el.getAttribute('data-loaded')) run();
+    else el.addEventListener('load', function(){ el.setAttribute('data-loaded','1'); run(); });
+  }
+})();
+</script>"""
     pre = PREAMBLE.render(title=f"{esc_html(f)} - {esc_html(project)}", theme="dark", site_name=config.SITE_NAME)
-    return HTMLResponse(
-        f"{pre}<pre class='blob-content'>{body}</pre>{POSTAMBLE}")
+    return HTMLResponse(f"{pre}{blob_html}{blob_script}{POSTAMBLE}")
 
 
 def git_tree(project: str, h: str | None, f: str | None) -> HTMLResponse:
diff --git a/pygitweb/static/main.css b/pygitweb/static/main.css
index 279fc21..d130120 100644
--- a/pygitweb/static/main.css
+++ b/pygitweb/static/main.css
@@ -128,3 +128,41 @@ tbody tr:hover {
 .diff-add { color: var(--pgw-add-fg); background-color: var(--pgw-add-bg); }
 .diff-rem { color: var(--pgw-rem-fg); background-color: var(--pgw-rem-bg); }
 .text-muted { color: var(--pgw-text-muted); }
+
+/* Blob view: line numbers + code with syntax highlighting */
+.blob-view {
+	display: flex;
+	margin: 0.5em 0;
+	max-width: 100%;
+	overflow: auto;
+	background-color: var(--pgw-code-bg);
+	border: 1px solid var(--pgw-code-border);
+}
+.blob-line-nums {
+	flex-shrink: 0;
+	user-select: none;
+	padding: 0.75em 1em 0.75em 0.5em;
+	text-align: right;
+	color: var(--pgw-text-muted);
+	white-space: pre;
+	font-family: ui-monospace, "Fira Code", "Fira Mono", monospace;
+	font-size: 0.9rem;
+	line-height: 1.5;
+	border-right: 1px solid var(--pgw-border);
+}
+.blob-view .blob-content {
+	flex: 1;
+	margin: 0;
+	padding: 0.75em 1em;
+	overflow: auto;
+	font-family: ui-monospace, "Fira Code", "Fira Mono", monospace;
+	font-size: 0.9rem;
+	line-height: 1.5;
+	min-width: 0;
+}
+.blob-view .blob-content code {
+	background: none;
+	padding: 0;
+	font-size: inherit;
+	line-height: inherit;
+}
diff --git a/pygitweb/templates/preamble.html b/pygitweb/templates/preamble.html
index 611c0f0..1e6c1fa 100644
--- a/pygitweb/templates/preamble.html
+++ b/pygitweb/templates/preamble.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html><html class="theme-{{theme}}"><head><title>{{title | default('DistGit')}}</title>
 <link rel="icon" href="static/logo.svg" type="image/svg+xml">
 <script id="Highlight-script" async src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
 <script id="MathJax-script" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-chtml.min.js"></script>
 <script id="GraphViz-script" async src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/1.8.2/viz.js"></script>
 <script id="diff2html-script" async src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
