diff --git a/pygitweb/README.md b/pygitweb/README.md
index 3e619f6..de66336 100644
--- a/pygitweb/README.md
+++ b/pygitweb/README.md
@@ -58,7 +58,8 @@ See [ROUTES.md](ROUTES.md) for more detail.
 - `GET /{project}?a=commit` — commit information
 - `GET /{project}?a=commitdiff` — commit diff (unified diff rendered with [diff2html](https://github.com/rtfpessoa/diff2html))
 - `GET /{project}?a=commitdiff_plain` — unified diff for commit (raw text)
+- `GET /{project}?a=remotes` — list repo remotes
 
 **Project-scoped actions (stub only)**
 
-These actions are accepted but return a minimal placeholder page. Add handlers in `main.py` to implement: `blame`, `blame_incremental`, `blame_data`, `blobdiff`, `blobdiff_plain`, `heads`, `patch`, `patches`, `remotes`, `rss`, `atom`, `search`, `search_help`, `snapshot`, `object`.
+These actions are accepted but return a minimal placeholder page. Add handlers in `main.py` to implement: `blame`, `blame_incremental`, `blame_data`, `blobdiff`, `blobdiff_plain`, `heads`, `patch`, `patches`, `rss`, `atom`, `search`, `search_help`, `snapshot`, `object`.
diff --git a/pygitweb/git_helpers.py b/pygitweb/git_helpers.py
index 5beb131..2c5bcf0 100644
--- a/pygitweb/git_helpers.py
+++ b/pygitweb/git_helpers.py
@@ -342,7 +342,24 @@ def git_get_remotes_list(project: str) -> list[str]:
     """Remote names. Port of git_get_remotes_list (pygit2 remotes)."""
     try:
         repo = open_repo(project)
-        return list(repo.remotes)
+        return list(repo.remotes.names())
+    except (pygit2.GitError, OSError):
+        return []
+
+
+def git_get_remotes_info(project: str) -> list[dict[str, Any]]:
+    """Remote info: name, url, push_url. Uses pygit2 Remote.url and Remote.push_url."""
+    try:
+        repo = open_repo(project)
+        result = []
+        for name in repo.remotes.names():
+            remote = repo.remotes[name]
+            result.append({
+                "name": name,
+                "url": remote.url or "",
+                "push_url": remote.push_url or remote.url or "",
+            })
+        return result
     except (pygit2.GitError, OSError):
         return []
 
diff --git a/pygitweb/main.py b/pygitweb/main.py
index 4a1c1be..e8e261d 100644
--- a/pygitweb/main.py
+++ b/pygitweb/main.py
@@ -40,6 +40,7 @@ from pygitweb.git_helpers import (
     get_tree_at_ref_path,
     git_get_head_hash,
     git_get_project_description,
+    git_get_remotes_info,
     git_get_tags_list,
     parse_commit,
     parse_tag,
@@ -508,6 +509,8 @@ def dispatch(
         return git_commitdiff_plain(proj, hash_param)
     if action == "commitdiff":
         return git_commitdiff(proj, hash_param)
+    if action == "remotes":
+        return git_remotes(proj)
     # Stub others with minimal response
     pre = PREAMBLE.render(title=f"{esc_html(action)} - {esc_html(proj)}", theme="dark", site_name=config.SITE_NAME)
     return HTMLResponse(
@@ -535,12 +538,35 @@ def git_summary(project: str) -> HTMLResponse:
             ["Owner", esc_html(owner)],
             [esc_html("HEAD"), f"<a href='/{project}?a=commit&h={quote(head or '', safe='')}'>{head_short or 'N/A'}</a>"],
             [esc_html("tree"), f"<a href='/{project}?a=tree&h={head or ''}'>browse</a>"],
+            ["Remotes", f"<a href='/{project}?a=remotes'>view remotes</a>"],
         ]
     )
     pre = PREAMBLE.render(title=f"{esc_html(config.SITE_NAME)} - {project}", theme="dark", site_name=config.SITE_NAME)
     return HTMLResponse(f"{pre}<h1>{esc_html(project)}</h1>{table}{POSTAMBLE}")
 
 
+def git_remotes(project: str) -> HTMLResponse:
+    """Remotes page: list configured remotes (name, url, push_url)."""
+    remotes = git_get_remotes_info(project)
+    if not remotes:
+        pre = PREAMBLE.render(title=f"Remotes - {esc_html(project)}", theme="dark", site_name=config.SITE_NAME)
+        return HTMLResponse(
+            f"{pre}<h1>Remotes</h1><p>No remotes configured.</p>{POSTAMBLE}"
+        )
+    rows = []
+    for r in remotes:
+        name = esc_html(r["name"])
+        url = esc_html(r["url"] or "—")
+        push_url = esc_html(r["push_url"] or "—")
+        rows.append([name, url, push_url])
+    table = env.get_template("table.html").render(
+        cols=["Name", "URL", "Push URL"],
+        rows=rows,
+    )
+    pre = PREAMBLE.render(title=f"Remotes - {esc_html(project)}", theme="dark", site_name=config.SITE_NAME)
+    return HTMLResponse(f"{pre}<h1>Remotes</h1>{table}{POSTAMBLE}")
+
+
 def _tree_url(project: str, h: str, f: str | None, a: str = "tree") -> str:
     """Build URL for tree or blob: /project?a=a&h=h&f=f with f quoted."""
     q = f"a={a}&h={quote(h, safe='')}"
