SharePointVault ~/gotchas/rest-api ⌕ Search 404 pages, error messages, scripts…Ctrl K ★ Star
M↓ README.mdM↓ weburl-plus-server-relative-url-doubles-the-site-path.md
sharepoint-vault›gotchas›rest-api›weburl-plus-server-relative-url-doubles-the-site-path.mdhistoryedit on GitHub
● GOTCHArest-api· 2 min read

webUrl + serverRelativeUrl doubles the site path

applies to: SharePoint Online, SharePoint Serverreviewed: 2026-09-24#rest-api#urls#spfx#data-quality

A server-relative URL already starts with the site path, so appending it to the web's absolute URL produces …/sites/projects/sites/projects/Lists/Tasks. Such an address can live for months as a display value or a grouping key and break only on the day it first becomes a link or a filter. Build object URLs in one helper, from the origin plus the server-relative path.

Server-relativní adresa už cestou webu začíná, takže připojená za absolutní adresu webu vyrobí …/sites/projects/sites/projects/Lists/Tasks. Taková adresa může měsíce přežívat jako zobrazovaná hodnota nebo klíč pro seskupení a rozbije se až ve chvíli, kdy z ní poprvé bude odkaz nebo filtr. Adresy objektů skládej v jednom helperu z originu a server-relativní cesty.

Symptom

A report or a history view shows object URLs that nobody has ever clicked. Everything works — until the same value is used as a query input for the first time. In our case a crawl-log lookup for a list with 108 entries came back "no records", and there was nothing wrong with the crawl log.

Cause

Two URL flavours meet in one expression:

const webUrl  = this.context.pageContext.web.absoluteUrl;  // https://contoso.sharepoint.com/sites/projects
const listRel = '/sites/projects/Lists/Tasks';              // what GetList('…') wants: server-relative

const wrong = webUrl + listRel;
// https://contoso.sharepoint.com/sites/projects/sites/projects/Lists/Tasks

The value was consistent, which is exactly why it survived: as a grouping key it grouped correctly, as a display value nobody read it closely, and nobody clicked it — a link would have been an instant 404. It failed only when it had to match something.

Fix

One helper, used everywhere an object's absolute URL is needed — key, report, deep link, filter:

function objectAbsUrl(webUrl: string, serverRelUrl: string): string {
  return new URL(webUrl).origin + serverRelUrl;
}

new URL(serverRelUrl, webUrl).href works too — a path that starts with / replaces the base's whole path — but it percent-encodes spaces while the concatenation keeps them as they came. Pick one form and compare decoded.

Notes

  • A value that is only displayed is not verified. Before an address becomes a query input, run the query once against an object you know has results. An empty answer on a known-positive control points at your input, not at the data.
  • Fix every place that builds the address, not just the one that failed: the history key, the report, the finding list and the filter had all been built the same way.
Was this helpful?ask or add your experience on GitHub →
Saved you an hour? A star on GitHub helps other SharePoint people find the vault.★ Star on GitHub
last reviewed: 2026-09-24last updated: 2026-09-24historyedit on GitHubview source