diff --git a/docs/EXPLORER_API_ACCESS.md b/docs/EXPLORER_API_ACCESS.md
index 732d7d3..1d2cc9e 100644
--- a/docs/EXPLORER_API_ACCESS.md
+++ b/docs/EXPLORER_API_ACCESS.md
@@ -13,6 +13,21 @@ The frontend is reachable at **https://explorer.d-bis.org** (FQDN) or by **VM IP
2. **Same-origin /api** – When the site is served from the explorer host (FQDN `https://explorer.d-bis.org` or VM IP `http://192.168.11.140` / `https://192.168.11.140`), the frontend uses relative `/api` so all requests go through the same nginx proxy. If you open the frontend from elsewhere, the code falls back to the full Blockscout URL (CORS must allow it).
- If the API returns **200** but the UI still shows no data, check the browser console for JavaScript errors (e.g. CSP or network errors).
+### Frontend env contract
+
+For the Next frontend in `frontend/`, keep the runtime base URL at the **host origin**, not the `/api` subpath:
+
+```env
+NEXT_PUBLIC_API_URL=https://explorer.d-bis.org
+NEXT_PUBLIC_CHAIN_ID=138
+```
+
+Why:
+
+- The Next pages now call live **Blockscout v2** endpoints under `${NEXT_PUBLIC_API_URL}/api/v2/*`.
+- Setting `NEXT_PUBLIC_API_URL=https://explorer.d-bis.org/api` will incorrectly produce requests like `/api/api/v2/*`.
+- Token aggregation remains under `/token-aggregation/api/v1/*` and is linked separately by the frontend.
+
---
## CSP blocks eval / “script-src blocked”
diff --git a/frontend/package.json b/frontend/package.json
index 010527b..70dfe56 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,9 +6,11 @@
"scripts": {
"dev": "next dev",
"build": "next build",
- "start": "next start",
+ "build:check": "npm run lint && npm run type-check && npm run build",
+ "start": "PORT=${PORT:-3000} node .next/standalone/server.js",
+ "start:next": "next start",
"lint": "next lint",
- "type-check": "tsc --noEmit",
+ "type-check": "tsc --noEmit -p tsconfig.check.json",
"test": "npm run lint && npm run type-check",
"test:unit": "vitest run"
},
diff --git a/frontend/public/explorer-spa.js b/frontend/public/explorer-spa.js
index 9625736..ab4e806 100644
--- a/frontend/public/explorer-spa.js
+++ b/frontend/public/explorer-spa.js
@@ -18,7 +18,7 @@
banner.id = 'apiUnavailableBanner';
banner.setAttribute('role', 'alert');
banner.style.cssText = 'background: rgba(200,80,80,0.95); color: #fff; padding: 0.75rem 1rem; margin-bottom: 1rem; border-radius: 8px; font-size: 0.9rem;';
- banner.innerHTML = 'Explorer API temporarily unavailable (HTTP ' + status + '). Stats, blocks, and transactions cannot load until the backend is running. See docs.';
+ banner.innerHTML = 'Explorer API temporarily unavailable (HTTP ' + status + '). Stats, blocks, and transactions cannot load until the backend is running. See docs.';
main.insertBefore(banner, main.firstChild);
}
(function() {
@@ -2016,7 +2016,7 @@
async function renderHomeView() {
showView('home');
- if ((window.location.pathname || '').replace(/^\//, '').replace(/\/$/, '') !== 'home') updatePath('/home');
+ if ((window.location.pathname || '').replace(/\/$/, '') !== '') updatePath('/');
await loadStats();
await loadLatestBlocks();
await loadLatestTransactions();
@@ -2115,7 +2115,7 @@
} else if (fromHash) {
route = fromHash;
}
- if (!route) { showHome(); updatePath('/home'); return; }
+ if (!route || route === 'home') { if (currentView !== 'home') showHome(); return; }
var parts = route.split('/').filter(Boolean);
var decode = function(s) { try { return decodeURIComponent(s); } catch (e) { return s; } };
if (parts[0] === 'block' && parts[1]) { var p1 = decode(parts[1]); var key = 'block:' + p1; if (currentDetailKey === key) return; currentDetailKey = key; setTimeout(function() { showBlockDetail(p1); }, 0); return; }
@@ -2204,7 +2204,7 @@
// Update breadcrumb navigation
function updateBreadcrumb(type, identifier, identifierExtra) {
let breadcrumbContainer;
- let breadcrumbHTML = 'Home';
+ let breadcrumbHTML = 'Home';
switch (type) {
case 'block':
breadcrumbContainer = document.getElementById('blockDetailBreadcrumb');
@@ -2222,7 +2222,7 @@
break;
case 'address':
breadcrumbContainer = document.getElementById('addressDetailBreadcrumb');
- breadcrumbHTML += '/Address/' + escapeHtml(shortenHash(identifier)) + '';
+ breadcrumbHTML += '/Addresses/' + escapeHtml(shortenHash(identifier)) + '';
break;
case 'token':
breadcrumbContainer = document.getElementById('tokenDetailBreadcrumb');
@@ -3716,7 +3716,7 @@
html += '