diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index e716e09b8b3f0..859746b6a2df7 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -459,7 +459,7 @@ impl Item {
             .filter_map(|ItemLink { link: s, link_text, did, ref fragment }| {
                 match did {
                     Some(did) => {
-                        if let Some((mut href, ..)) = href(did.clone(), cx) {
+                        if let Ok((mut href, ..)) = href(did.clone(), cx) {
                             if let Some(ref fragment) = *fragment {
                                 href.push('#');
                                 href.push_str(fragment);
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index c08fe47696bf1..08499cef33ef1 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -472,7 +472,19 @@ impl clean::GenericArgs {
     }
 }
 
-crate fn href(did: DefId, cx: &Context<'_>) -> Option<(String, ItemType, Vec<String>)> {
+// Possible errors when computing href link source for a `DefId`
+crate enum HrefError {
+    /// This item is known to rustdoc, but from a crate that does not have documentation generated.
+    ///
+    /// This can only happen for non-local items.
+    DocumentationNotBuilt,
+    /// This can only happen for non-local items when `--document-private-items` is not passed.
+    Private,
+    // Not in external cache, href link should be in same page
+    NotInExternalCache,
+}
+
+crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<String>), HrefError> {
     let cache = &cx.cache();
     let relative_to = &cx.current;
     fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
@@ -480,7 +492,7 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Option<(String, ItemType, Vec<Str
     }
 
     if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private {
-        return None;
+        return Err(HrefError::Private);
     }
 
     let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
@@ -489,22 +501,25 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Option<(String, ItemType, Vec<Str
             href_relative_parts(module_fqp, relative_to)
         }),
         None => {
-            let &(ref fqp, shortty) = cache.external_paths.get(&did)?;
-            let module_fqp = to_module_fqp(shortty, fqp);
-            (
-                fqp,
-                shortty,
-                match cache.extern_locations[&did.krate] {
-                    ExternalLocation::Remote(ref s) => {
-                        let s = s.trim_end_matches('/');
-                        let mut s = vec![&s[..]];
-                        s.extend(module_fqp[..].iter().map(String::as_str));
-                        s
-                    }
-                    ExternalLocation::Local => href_relative_parts(module_fqp, relative_to),
-                    ExternalLocation::Unknown => return None,
-                },
-            )
+            if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&did) {
+                let module_fqp = to_module_fqp(shortty, fqp);
+                (
+                    fqp,
+                    shortty,
+                    match cache.extern_locations[&did.krate] {
+                        ExternalLocation::Remote(ref s) => {
+                            let s = s.trim_end_matches('/');
+                            let mut s = vec![&s[..]];
+                            s.extend(module_fqp[..].iter().map(String::as_str));
+                            s
+                        }
+                        ExternalLocation::Local => href_relative_parts(module_fqp, relative_to),
+                        ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
+                    },
+                )
+            } else {
+                return Err(HrefError::NotInExternalCache);
+            }
         }
     };
     let last = &fqp.last().unwrap()[..];
@@ -518,7 +533,7 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Option<(String, ItemType, Vec<Str
             url_parts.push(&filename);
         }
     }
-    Some((url_parts.join("/"), shortty, fqp.to_vec()))
+    Ok((url_parts.join("/"), shortty, fqp.to_vec()))
 }
 
 /// Both paths should only be modules.
@@ -567,7 +582,7 @@ fn resolved_path<'a, 'cx: 'a>(
         write!(w, "{}{:#}", &last.name, last.args.print(cx))?;
     } else {
         let path = if use_absolute {
-            if let Some((_, _, fqp)) = href(did, cx) {
+            if let Ok((_, _, fqp)) = href(did, cx) {
                 format!(
                     "{}::{}",
                     fqp[..fqp.len() - 1].join("::"),
@@ -675,7 +690,7 @@ crate fn anchor<'a, 'cx: 'a>(
 ) -> impl fmt::Display + 'a {
     let parts = href(did.into(), cx);
     display_fn(move |f| {
-        if let Some((url, short_ty, fqp)) = parts {
+        if let Ok((url, short_ty, fqp)) = parts {
             write!(
                 f,
                 r#"<a class="{}" href="{}" title="{} {}">{}</a>"#,
@@ -907,7 +922,7 @@ fn fmt_type<'cx>(
                 //        look at).
                 box clean::ResolvedPath { did, .. } => {
                     match href(did.into(), cx) {
-                        Some((ref url, _, ref path)) if !f.alternate() => {
+                        Ok((ref url, _, ref path)) if !f.alternate() => {
                             write!(
                                 f,
                                 "<a class=\"type\" href=\"{url}#{shortty}.{name}\" \
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 97ee682c11c4d..09760e4063b11 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -62,7 +62,7 @@ use crate::formats::{AssocItemRender, Impl, RenderMode};
 use crate::html::escape::Escape;
 use crate::html::format::{
     href, print_abi_with_space, print_constness_with_space, print_default_space,
-    print_generic_bounds, print_where_clause, Buffer, PrintWithSpace,
+    print_generic_bounds, print_where_clause, Buffer, HrefError, PrintWithSpace,
 };
 use crate::html::markdown::{Markdown, MarkdownHtml, MarkdownSummaryLine};
 
@@ -856,8 +856,8 @@ fn render_assoc_item(
     ) {
         let name = meth.name.as_ref().unwrap();
         let href = match link {
-            AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id),
-            AssocItemLink::Anchor(None) => format!("#{}.{}", meth.type_(), name),
+            AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)),
+            AssocItemLink::Anchor(None) => Some(format!("#{}.{}", meth.type_(), name)),
             AssocItemLink::GotoSource(did, provided_methods) => {
                 // We're creating a link from an impl-item to the corresponding
                 // trait-item and need to map the anchored type accordingly.
@@ -867,9 +867,11 @@ fn render_assoc_item(
                     ItemType::TyMethod
                 };
 
-                href(did.expect_def_id(), cx)
-                    .map(|p| format!("{}#{}.{}", p.0, ty, name))
-                    .unwrap_or_else(|| format!("#{}.{}", ty, name))
+                match (href(did.expect_def_id(), cx), ty) {
+                    (Ok(p), ty) => Some(format!("{}#{}.{}", p.0, ty, name)),
+                    (Err(HrefError::DocumentationNotBuilt), ItemType::TyMethod) => None,
+                    (Err(_), ty) => Some(format!("#{}.{}", ty, name)),
+                }
             }
         };
         let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string();
@@ -904,7 +906,7 @@ fn render_assoc_item(
         w.reserve(header_len + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len());
         write!(
             w,
-            "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a href=\"{href}\" class=\"fnname\">{name}</a>\
+            "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a {href} class=\"fnname\">{name}</a>\
              {generics}{decl}{notable_traits}{where_clause}",
             indent = indent_str,
             vis = vis,
@@ -913,7 +915,8 @@ fn render_assoc_item(
             unsafety = unsafety,
             defaultness = defaultness,
             abi = abi,
-            href = href,
+            // links without a href are valid - https://www.w3schools.com/tags/att_a_href.asp
+            href = href.map(|href| format!("href=\"{}\"", href)).unwrap_or_else(|| "".to_string()),
             name = name,
             generics = g.print(cx),
             decl = d.full_print(header_len, indent, header.asyncness, cx),
diff --git a/src/test/rustdoc/auxiliary/issue-86620-1.rs b/src/test/rustdoc/auxiliary/issue-86620-1.rs
new file mode 100644
index 0000000000000..f6debf6fb4e63
--- /dev/null
+++ b/src/test/rustdoc/auxiliary/issue-86620-1.rs
@@ -0,0 +1,11 @@
+#![crate_name = "issue_86620_1"]
+
+pub trait VZip {
+    fn vzip() -> usize;
+}
+
+impl<T> VZip for T {
+    fn vzip() -> usize {
+        0
+    }
+}
diff --git a/src/test/rustdoc/extern-default-method.rs b/src/test/rustdoc/extern-default-method.rs
index 810c591c53e1e..efa2025b4b9c2 100644
--- a/src/test/rustdoc/extern-default-method.rs
+++ b/src/test/rustdoc/extern-default-method.rs
@@ -4,4 +4,6 @@
 extern crate rustdoc_extern_default_method as ext;
 
 // @count extern_default_method/struct.Struct.html '//*[@id="method.provided"]' 1
+// @has extern_default_method/struct.Struct.html '//div[@id="method.provided"]//a[@class="fnname"]/@href' #method.provided
+// @has extern_default_method/struct.Struct.html '//div[@id="method.provided"]//a[@class="anchor"]/@href' #method.provided
 pub use ext::Struct;
diff --git a/src/test/rustdoc/issue-86620.rs b/src/test/rustdoc/issue-86620.rs
new file mode 100644
index 0000000000000..b14e266f7f98d
--- /dev/null
+++ b/src/test/rustdoc/issue-86620.rs
@@ -0,0 +1,9 @@
+// aux-build:issue-86620-1.rs
+
+extern crate issue_86620_1;
+
+use issue_86620_1::*;
+
+// @!has issue_86620/struct.S.html '//div[@id="method.vzip"]//a[@class="fnname"]/@href' #tymethod.vzip
+// @has issue_86620/struct.S.html '//div[@id="method.vzip"]//a[@class="anchor"]/@href' #method.vzip
+pub struct S;