{"componentChunkName":"component---src-templates-post-jsx","path":"/blog/how-to-find-broken-links-with-polypane/","result":{"data":{"mdx":{"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nconst _frontmatter = {\n  \"title\": \"How to find broken links with Polypane\",\n  \"cover\": \"/blogs/brokenlinks/cover.svg\",\n  \"date\": \"2021-04-12\",\n  \"updated\": \"2024-05-28\",\n  \"type\": [\"tutorial\"]\n};\nconst layoutProps = {\n  _frontmatter\n};\nconst MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  let {\n    components\n  } = _ref,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, `Broken links, or \"dead links\", on your website are links that go to a URL that doesn't work. Sometimes this is because the\nsite you're linking to has gone offline or has a server error, sometimes it's because there is a spelling mistake in your link.`), mdx(\"p\", null, `Detecting these\nis difficult: you don't click on each url on each page every time you make a change, that would take too\nmuch time.`), mdx(\"p\", null, `Waiting for your visitors to tell you about broken links is also not a great strategy. Sending them to a URL that doesn't\nwork and then waiting for them to tell you is not a great user experience, and very few visitors will take the effort to\nnotify you.`), mdx(\"p\", null, `Additionally, a page that has broken links is an indicator for search engines that the content is probably\noutdated or low quality.`), mdx(\"p\", null, `With Polypane, you can check for broken links automatically and detect not just broken links but also unneeded redirects.`), mdx(\"p\", null, `Before we show you how to check for broken links, first a short primer:`), mdx(\"h2\", {\n    \"id\": \"types-of-broken-links\"\n  }, `Types of broken links`), mdx(\"p\", null, `Whenever you open a URL, the server responds with a status code. This status code falls in one of four categories: 2xx, 3xx, 4xx or 5xx, where xx are two numbers.`), mdx(\"p\", null, `any status code starting with a 2 is a success, the server got your request and responded with the correct information. Any status code starting with a 3 is a redirect, the server is telling you to go somewhere else.`), mdx(\"p\", null, `The 4xx and 5xx range are errors, and they say something about who made the error:`), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, `A 400 status code means `, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `you`), ` made an error.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, `A 500 status code means `, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `the server`), ` made an error.`)), mdx(\"p\", null, `For example, a \"404 not found\" means that while the server got and understood your requests, the URL you tried to open\nwas not there. A \"500 internal server error\" means there was an error in the code of the server that it couldn't recover from.`), mdx(\"p\", null, `Those two are the most important, but if you're checking for broken links there are a few others you might encounter in\nthe wild. While this isn't an exhaustive list, they're the ones you'll see most often.`), mdx(\"h3\", {\n    \"id\": \"4xx-errors-aka-you-messed-up\"\n  }, `4xx errors, a.k.a. \"You messed up\"`), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `400 Bad Request`), ` There was something wrong with the request, like an error in the request syntax.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `401 Unauthorized/403 Forbidden`), ` Both can be used to indicate a user does not have the rights to access this link, for example because they're not logged in yet.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `404 Not Found`), ` as mentioned, this means the URL doesn't exist.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `405 Method not Allowed`), ` Some URLS can only be accessed with a GET or a POST, but you used a different method.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `410 Gone`), ` Like a 404, but explicity communicates that there `, mdx(\"em\", {\n    parentName: \"li\"\n  }, `was`), ` a resource and it's now been removed.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `429 Too Many Requests`), ` Some servers might rate limit how often you can open a URL to prevent abuse, they can use this status code.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `451 Unavailable for Legal Reasons`), ` When a URL is not available because of some legal demand. \"Fun\" fact: the error code is a reference to the novel `, mdx(\"em\", {\n    parentName: \"li\"\n  }, `Fahrenheit 451`), `.`)), mdx(\"h3\", {\n    \"id\": \"5xx-errors-aka-we-messed-up\"\n  }, `5xx errors, a.k.a. \"We messed up\"`), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `500 Internal Server Error`), ` As mentioned, the server crashed while trying to respond.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `501 Not Implemented`), ` Usually used to indicate something will eventually be implemented.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `502 Bad Gateway`), ` When the server is used as a proxy or gateway, but it got a bad response from the server it was proxying too. For example when you run Nginx as a proxy for a Django, Rails or Express.js server, and that server is down.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `503 Service Unavailable`), ` Usually sent when the server is over capacity (too many requests, not enough memory, etc.)`)), mdx(\"p\", null, `There's (much) more error codes than just these (like `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `418 I'm a teapot`), `, look it up), but they're the ones you see most often \"in the wild\".`), mdx(\"h3\", {\n    \"id\": \"3xx-status-codes-look-elsewhere\"\n  }, `3xx status codes: \"Look elsewhere\"`), mdx(\"p\", null, `When you start testing for broken links you'll soon find URLs returning a code in the 3xx range. Those are used to indicate\na resource has moved, either temporarily or permanently.`), mdx(\"p\", null, `While not a big issue (the URLs still work) these are nice to fix, as\nyou don't know if the redirects will be available forever, and there is a (tiny) performance cost because your browser\nneeds to send a second request to the correct URL.`), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `301 Moved Permanently`), ` Used to indicate that the resource has moved to this new location forever. Due to the way the spec was written, a 301 can switch from a POST to a GET.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `302 Found`), ` Used to indicate that the resource is now moved elsewhere. While this one is still in widespread use, you should really use either 303 or 307.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `303 See Other`), ` Replaces 302 specifically in the situations where you can GET the resource elsewhere.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `304 Not Modified`), ` If the resource hasn't changed, your browser can safely get it from the Cache for a faster turnaround.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `307 Temporary redirect`), ` Same as 302 Found but more explicit in its intent. You can not change the HTTP Method (so a POST stays a POST).`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `308 Permanent redirect`), ` Same as 301 Moved Permanently but more explicit in its intent. You can not change the HTTP Method (so a POST stays a POST).`)), mdx(\"p\", null, `Some of these are a little vague and seem to overlap, because some details do not really have a material effect on how your browser behaves.`), mdx(\"p\", null, `In practice, you'll see `, mdx(\"strong\", {\n    parentName: \"p\"\n  }, `301`), ` and `, mdx(\"strong\", {\n    parentName: \"p\"\n  }, `302`), ` the most, though I assume that will slowly switch over to `, mdx(\"strong\", {\n    parentName: \"p\"\n  }, `307`), ` and `, mdx(\"strong\", {\n    parentName: \"p\"\n  }, `308`), `.`), mdx(\"p\", null, `With that little primer out of the way, lets discover how to find and fix broken links in Polypane.`), mdx(\"h2\", {\n    \"id\": \"broken-links-in-the-outline-panel\"\n  }, `Broken links in the Outline panel`), mdx(\"p\", null, `When you open a page in Polypane, the `, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"/docs/outline-panel/\"\n  }), `Outline panel`), ` parses it to find all the links on the page. To head to the Outline panel,\nfirst open the side panel (with `, mdx(ShortcutDisplay, {\n    shiftKey: true,\n    cmdKey: true,\n    letter: \"p\",\n    mdxType: \"ShortcutDisplay\"\n  }), `, by clicking the side panel button in the header or by using the `, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"/docs/command-bar/\"\n  }), `command bar`), `, going to \"Info\" and then to the \"Outline\" tab.`), mdx(\"img\", {\n    src: outline,\n    alt: \"The top of the outline panel\",\n    className: \"imgshadow\",\n    style: {\n      \"margin\": \"2rem auto\",\n      \"maxWidth\": \"100%\"\n    }\n  }), mdx(\"p\", null, `By default, Polypane shows the list of headers. Click the dropdown to switch to \"links\":`), mdx(\"img\", {\n    alt: \"Link overview\",\n    src: \"/blogs/brokenlinks/initial.png\",\n    className: \"imgshadow\",\n    style: {\n      margin: '2rem auto',\n      display: 'block',\n      maxWidth: '100%'\n    }\n  }), mdx(\"p\", null, `The links are shown in source order, and you can see the (accessible) link text, the URL it points to, and any attributes it has. Polypane then checks each URL to detect the status code it returns.`), mdx(\"p\", null, `When it's done, it will prefix the status to each URL, each with a different design so you can quickly see what's going on:`), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, `White text on a red background is a big issue, like a broken link.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, `Red text is a minor issue, like a redirect or server issue.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, `A checkmark means everything is okay (we got a 200 status code).`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, `A question mark means we couldn't verify a url because it was blocked or returned a 429 status code.`)), mdx(\"h3\", {\n    \"id\": \"fixing-big-issues\"\n  }, `Fixing big issues`), mdx(\"p\", null, `Fixing broken links (those with a 404) should be your highest priority:`), mdx(\"img\", {\n    alt: \"Example of the broken links\",\n    src: \"/blogs/brokenlinks/brokenlinks.png\",\n    className: \"imgshadow\",\n    style: {\n      margin: '2rem auto',\n      display: 'block',\n      maxWidth: '100%'\n    }\n  }), mdx(\"p\", null, `Find out why a url is now offline and where it was moved too (old urls not properly redirecting happens more often than\nyou think), find a suitable alternative or remove the link from your page altogether.`), mdx(\"h3\", {\n    \"id\": \"fixing-minor-issues\"\n  }, `Fixing minor issues`), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, `Server issues`), ` (Those starting with a 5) are problems but usually not something you can directly do something about (unless it happens on your server!)\nIf the server error comes from a third party website, be kind and let them know which URLs are broken (or point them to Polypane. ;)`), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, `Redirects`), ` (those starting with a 3) shouldn't be a high priority for you, but usually they're easy to fix.`), mdx(\"p\", null, `A really common one for example is that the URL in the `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `href`), ` doens't end with `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `/`), `, but the server automatically adding one to all URLs.`), mdx(\"p\", null, `For example, the \"articles\" link in the main navigation on Smashing Magazine is missing that `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `/`), `, because the actual URL is `, mdx(\"code\", _extends({\n    parentName: \"p\"\n  }, {\n    \"className\": \"language-text\"\n  }), `/articles/`), `:`), mdx(\"img\", {\n    alt: \"Example of an unneeded redirect\",\n    src: \"/blogs/brokenlinks/301.png\",\n    className: \"imgshadow\",\n    style: {\n      margin: '2rem auto',\n      display: 'block',\n      maxWidth: '100%'\n    }\n  }), mdx(\"p\", null, `Not all redirects can be solved however, as some might be contextual. The links to our dashboard gets a 302 because it either\nredirects you to log in or forwards you to your account overview:`), mdx(\"img\", {\n    alt: \"Contextual redirect with 302\",\n    src: \"/blogs/brokenlinks/contextual.png\",\n    className: \"imgshadow\",\n    style: {\n      margin: '2rem auto',\n      display: 'block',\n      maxWidth: '100%'\n    }\n  }), mdx(\"h2\", {\n    \"id\": \"other-issues-with-links\"\n  }, `Other issues with links`), mdx(\"p\", null, `Your links might have some other issues that the Outline panel will highlight for you:`), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `Non-https links`), ` are unsafe and you will probably want to fix those.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `Empty `, mdx(\"code\", _extends({\n    parentName: \"strong\"\n  }, {\n    \"className\": \"language-text\"\n  }), `href`), ` attributes`), ` are a common mistake, and Polypane will show a warning for them.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `Placeholder values`), ` like `, mdx(\"code\", _extends({\n    parentName: \"li\"\n  }, {\n    \"className\": \"language-text\"\n  }), `mailto:`), ` or `, mdx(\"code\", _extends({\n    parentName: \"li\"\n  }, {\n    \"className\": \"language-text\"\n  }), `#`), ` are also shown with a warning.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `Non-descriptive text`), ` like \"click here\" or \"more\" are shown with a warning.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `Internal links that have no corresponding element`), ` A link to `, mdx(\"code\", _extends({\n    parentName: \"li\"\n  }, {\n    \"className\": \"language-text\"\n  }), `#section`), ` is only valid if there is an element with the id `, mdx(\"code\", _extends({\n    parentName: \"li\"\n  }, {\n    \"className\": \"language-text\"\n  }), `section`), ` on the page.`), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, `Inconsistent naming`), ` If you have multiple links with the same URL but different text, or multiple links with the same text but different URLs, Polypane will show a warning and a list of the mismatched links.`)), mdx(\"h3\", {\n    \"id\": \"not-using-https\"\n  }, `Not using HTTPS`), mdx(\"p\", null, `Using HTTPS makes the web more secure and more private, but it's an easy mistake to link to the HTTP version of a URL\nsince it usually forwards to HTTPS anyway.`), mdx(\"p\", null, `It's such an easy mistake, the offical website of the White House made it right in the middle of their homepage (this has since been fixed):`), mdx(\"img\", {\n    alt: \"Screenshot of the whitehouse.gov website with the link to Twitter circled\",\n    src: \"/blogs/brokenlinks/nohttps.jpg\",\n    className: \"imgshadow\",\n    style: {\n      margin: '2rem auto',\n      display: 'block',\n      maxWidth: '100%'\n    }\n  }), mdx(\"img\", {\n    alt: \"That same link with a 'no-https' badge in the Outline panel\",\n    src: \"/blogs/brokenlinks/nohttps2.png\",\n    className: \"imgshadow\",\n    style: {\n      margin: '2rem auto',\n      display: 'block',\n      maxWidth: '100%'\n    }\n  }), mdx(\"p\", null, `Any URL that does not use HTTPS will have a \"non-HTTPS\" badge visible. For example, the link to\n`, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://latinotype.com\"\n  }), `Latinotype`), ` in the `, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://smashingmagazine.com\"\n  }), `SmashingMagazine footer`), ` is using HTTP, despite\nboth sites supporting HTTPS:`), mdx(\"img\", {\n    alt: \"Example of the 'no HTTPS' badge\",\n    src: \"/blogs/brokenlinks/nohttps.png\",\n    className: \"imgshadow\",\n    style: {\n      margin: '2rem auto',\n      display: 'block',\n      maxWidth: '100%'\n    }\n  }), mdx(\"p\", null, `For a complete overview of everything the Polypane Outline panel does, `, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"/docs/outline-panel/\"\n  }), `check out the documentation`), `.`), mdx(\"h2\", {\n    \"id\": \"try-it-for-yourself\"\n  }, `Try it for yourself`), mdx(\"p\", null, `Polypane is available with a 14 day free trial, enough time to check all the pages on your site. `, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://dashboard.polypane.app/register\"\n  }), `Start a trial now`), `.`));\n}\n;\nMDXContent.isMDXComponent = true;","timeToRead":6,"tableOfContents":{"items":[{"url":"#types-of-broken-links","title":"Types of broken links","items":[{"url":"#4xx-errors-aka-you-messed-up","title":"4xx errors, a.k.a. \"You messed up\""},{"url":"#5xx-errors-aka-we-messed-up","title":"5xx errors, a.k.a. \"We messed up\""},{"url":"#3xx-status-codes-look-elsewhere","title":"3xx status codes: \"Look elsewhere\""}]},{"url":"#broken-links-in-the-outline-panel","title":"Broken links in the Outline panel","items":[{"url":"#fixing-big-issues","title":"Fixing big issues"},{"url":"#fixing-minor-issues","title":"Fixing minor issues"}]},{"url":"#other-issues-with-links","title":"Other issues with links","items":[{"url":"#not-using-https","title":"Not using HTTPS"}]},{"url":"#try-it-for-yourself","title":"Try it for yourself"}]},"excerpt":"Broken links, or \"dead links\", on your website are links that go to a URL that doesn't work. Sometimes this is because the\nsite you're linking to has gone…","frontmatter":{"title":"How to find broken links with Polypane","cover":"/blogs/brokenlinks/cover.svg","date":"2021-04-12","updated":"2024-05-28"},"fields":{"slug":"/blog/how-to-find-broken-links-with-polypane/","date":"2021-04-11T22:00:00.000Z","ogFileName":"how-to-find-broken-links-with-polypane"}}},"pageContext":{"slug":"/blog/how-to-find-broken-links-with-polypane/"}},"staticQueryHashes":["4164364741","425175329"]}