[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-url-parse-safe-parsing":32,"code:js:true:1gu8748":250,"code:js:true:1x05vo":251,"code:js:true:15hp1mm":252,"code:js:true:1b877ss":253,"code:js:true:s08dzp":254,"code:ts:true:7gd9ca":255},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"id":33,"slug":34,"title":35,"subtitle":36,"excerpt":37,"coverUrl":38,"locale":13,"readingMinutes":39,"publishedAt":40,"viewCount":41,"likeCount":19,"commentCount":19,"author":42,"vertical":47,"topic":48,"tags":51,"_count":63,"playground":65,"body":67,"bodyMd":232,"seo":233,"translationGroupId":237,"thread":238,"assessments":240,"translations":246,"quiz":248},"019fe660-b8d7-7515-892c-bd576110158c","url-parse-safe-parsing","You've been wrapping `new URL()` in try\u002Fcatch. `URL.parse()` does it natively.",null,"Validating an untrusted URL before parsing it used to mean a try\u002Fcatch wrapper or a regex. `URL.canParse()` checks validity without constructing. `URL.parse()` parses and returns null on failure. Both are Baseline 2024 — no wrapper needed.","\u002Fmedia\u002Fcovers\u002Furl-parse-safe-parsing.png",5,"2026-08-01T08:27:13.710Z",77,{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},"javascript","JavaScript",[52,54,57,60],{"slug":49,"name":53,"color":36},"Javascript",{"slug":55,"name":56,"color":36},"webdev","Webdev",{"slug":58,"name":59,"color":36},"frontend","Frontend",{"slug":61,"name":62,"color":36},"typescript","Typescript",{"assessments":64},1,{"slug":34,"title":66},"URL.parse() vs new URL() — safe URL parsing",{"blocks":68,"version":64},[69,73,78,81,84,90,93,97,100,104,107,110,114,117,121,124,127,131,134,137,140,143,146,149,152,159,162,166,169,172,175,180,183,186,191,194,197,200,203,206,209,212,215,218,221,224],{"id":70,"html":71,"type":72},"b1","\u003Cp>Every codebase has this utility function somewhere. You wrote it the first time an untrusted URL caused an uncaught exception, or when a form field needed validation before submission, or when an API returned a path instead of a full URL. It&#39;s the try\u002Fcatch wrapper, and it works. The problem is that every team writes their own version, they differ in subtle ways, and they shouldn&#39;t need to exist. \u003Ccode>URL.canParse()\u003C\u002Fcode> and \u003Ccode>URL.parse()\u003C\u002Fcode> are the native replacements — shipping now in every modern browser.\u003C\u002Fp>","paragraph",{"id":74,"html":75,"text":76,"type":77,"level":31},"b2","The wrapper you&#39;ve been maintaining","The wrapper you've been maintaining","heading",{"id":79,"html":80,"type":72},"b3","\u003Cp>The \u003Ccode>new URL()\u003C\u002Fcode> constructor throws a \u003Ccode>TypeError\u003C\u002Fcode> on invalid input. That&#39;s correct when your input is a string literal you own — an exception is the right signal that your own code has a bug. It&#39;s the wrong behavior when you&#39;re parsing user input, a third-party API response, or a redirect URL from an HTTP header.\u003C\u002Fp>",{"id":82,"html":83,"type":72},"b4","\u003Cp>The result is this pattern:\u003C\u002Fp>",{"id":85,"code":86,"type":87,"language":88,"highlight":89},"b5","function safeParseURL(str, base) {\n  try {\n    return new URL(str, base);\n  } catch {\n    return null;\n  }\n}","code","js",[],{"id":91,"html":92,"type":72},"b6","\u003Cp>A bad URL in a form field is not a programmer error. Exceptions used for expected control flow make call sites harder to read and suppress details that would help debugging. This wrapper should not need to exist.\u003C\u002Fp>",{"id":94,"html":95,"text":96,"type":77,"level":31},"b7","\u003Ccode>URL.canParse()\u003C\u002Fcode> — validate without constructing","URL.canParse() — validate without constructing",{"id":98,"html":99,"type":72},"b8","\u003Cp>When you only need to know whether a string is a valid URL — form validation, link display logic, redirect safety checks — \u003Ccode>URL.canParse()\u003C\u002Fcode> gives you a boolean with no allocation:\u003C\u002Fp>",{"id":101,"code":102,"type":87,"language":88,"highlight":103},"b9","URL.canParse('https:\u002F\u002Fexample.com')           \u002F\u002F true\nURL.canParse('not a url')                     \u002F\u002F false\nURL.canParse('javascript:alert(1)')           \u002F\u002F true — valid URL, unsafe redirect\nURL.canParse('\u002Fabout', 'https:\u002F\u002Fexample.com') \u002F\u002F true\nURL.canParse('::bad', 'https:\u002F\u002Fexample.com')  \u002F\u002F false",[],{"id":105,"html":106,"type":72},"b10","\u003Cp>The second argument is a base URL, resolved the same way \u003Ccode>new URL()\u003C\u002Fcode> resolves it. This matters when you&#39;re validating relative paths — from a \u003Ccode>&lt;base&gt;\u003C\u002Fcode> tag, from API responses that return paths rather than full URLs, or from a \u003Ccode>&lt;a href&gt;\u003C\u002Fcode> value you want to check before following.\u003C\u002Fp>",{"id":108,"html":109,"type":72},"b11","\u003Cp>One important note: \u003Ccode>URL.canParse()\u003C\u002Fcode> validates the URL&#39;s \u003Cem>syntax\u003C\u002Fem>. It tells you whether the string can be parsed as a URL. It does not tell you whether the URL is safe to navigate to — \u003Ccode>javascript:\u003C\u002Fcode> and \u003Ccode>data:\u003C\u002Fcode> URLs are syntactically valid.\u003C\u002Fp>",{"id":111,"html":112,"text":113,"type":77,"level":31},"b12","\u003Ccode>URL.parse()\u003C\u002Fcode> — the non-throwing constructor","URL.parse() — the non-throwing constructor",{"id":115,"html":116,"type":72},"b13","\u003Cp>\u003Ccode>URL.parse()\u003C\u002Fcode> does what \u003Ccode>new URL()\u003C\u002Fcode> does but returns \u003Ccode>null\u003C\u002Fcode> instead of throwing when the input is invalid:\u003C\u002Fp>",{"id":118,"code":119,"type":87,"language":88,"highlight":120},"b14","const url = URL.parse(userInput);\nif (!url) {\n  showError('Not a valid URL.');\n  return;\n}\n\u002F\u002F url is a URL instance — all properties and methods are available\nconsole.log(url.hostname);\nconsole.log(url.pathname);\nurl.searchParams.set('ref', 'footer');",[],{"id":122,"html":123,"type":72},"b15","\u003Cp>Every property and method available on a \u003Ccode>URL\u003C\u002Fcode> object — \u003Ccode>hostname\u003C\u002Fcode>, \u003Ccode>pathname\u003C\u002Fcode>, \u003Ccode>searchParams\u003C\u002Fcode>, \u003Ccode>href\u003C\u002Fcode> — works the same way. The only difference from \u003Ccode>new URL()\u003C\u002Fcode> is what happens on invalid input: null instead of a thrown exception.\u003C\u002Fp>",{"id":125,"html":126,"type":72},"b16","\u003Cp>Like \u003Ccode>URL.canParse()\u003C\u002Fcode>, the second argument is a base URL:\u003C\u002Fp>",{"id":128,"code":129,"type":87,"language":88,"highlight":130},"b17","const url = URL.parse('\u002Fproducts\u002F42', 'https:\u002F\u002Fexample.com');\n\u002F\u002F → URL { href: 'https:\u002F\u002Fexample.com\u002Fproducts\u002F42', pathname: '\u002Fproducts\u002F42', ... }\n\nconst relative = URL.parse('..\u002Fimages\u002Fphoto.jpg', 'https:\u002F\u002Fcdn.example.com\u002Fassets\u002Fv2\u002F');\n\u002F\u002F → URL { href: 'https:\u002F\u002Fcdn.example.com\u002Fassets\u002Fimages\u002Fphoto.jpg', ... }",[],{"id":132,"html":133,"type":72},"b18","\u003C!-- playground:start -->",{"id":135,"html":136,"text":136,"type":77,"level":31},"b19","🎮 Try it yourself",{"id":138,"html":139,"type":72},"b20","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-08-01-url-parse-safe-parsing\u002Fplayground\u002F\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":141,"html":142,"type":72},"b21","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":144,"html":145,"type":72},"b22","\u003C!-- playground:end -->",{"id":147,"html":148,"text":148,"type":77,"level":31},"b23","Which to use and when",{"id":150,"html":151,"type":72},"b24","\u003Cp>There are now three ways to get a URL object, and each has a clear use case:\u003C\u002Fp>",{"id":153,"type":154,"items":155,"ordered":18},"b25","list",[156,157,158],"\u003Cstrong>\u003Ccode>new URL(str)\u003C\u002Fcode>\u003C\u002Fstrong> — when the input is yours and invalid input is a bug. Use this for URL construction from values you control. The thrown exception surfaces real mistakes immediately.","\u003Cstrong>\u003Ccode>URL.canParse(str)\u003C\u002Fcode>\u003C\u002Fstrong> — when you only need validity, not the URL object. Form validation, display logic, redirect checking. Zero allocation — it doesn&#39;t construct anything.","\u003Cstrong>\u003Ccode>URL.parse(str)\u003C\u002Fcode>\u003C\u002Fstrong> — when you need the URL object and the input might be invalid. This replaces every \u003Ccode>safeParseURL()\u003C\u002Fcode> wrapper in your codebase.",{"id":160,"html":161,"type":72},"b26","\u003Cp>The common antipattern to avoid: calling \u003Ccode>URL.canParse()\u003C\u002Fcode> immediately before \u003Ccode>URL.parse()\u003C\u002Fcode> or \u003Ccode>new URL()\u003C\u002Fcode>. That parses the string twice.\u003C\u002Fp>",{"id":163,"code":164,"type":87,"language":88,"highlight":165},"b27","\u002F\u002F Antipattern: double-parse\nif (URL.canParse(input)) {\n  const url = new URL(input); \u002F\u002F parsing the same string again\n  use(url);\n}\n\n\u002F\u002F Correct: parse once, check for null\nconst url = URL.parse(input);\nif (url) {\n  use(url);\n}",[],{"id":167,"html":168,"type":72},"b28","\u003Cp>Reach for \u003Ccode>URL.canParse()\u003C\u002Fcode> when null is the only thing you need to check — for example, gating a conditional that doesn&#39;t need the URL properties at all.\u003C\u002Fp>",{"id":170,"html":171,"text":171,"type":77,"level":31},"b29","TypeScript support",{"id":173,"html":174,"type":72},"b30","\u003Cp>TypeScript added type signatures for both methods in \u003Cstrong>version 5.1\u003C\u002Fstrong> (May 2023). No extra \u003Ccode>@types\u003C\u002Fcode> package is needed — if your \u003Ccode>tsconfig.json\u003C\u002Fcode> includes \u003Ccode>&quot;DOM&quot;\u003C\u002Fcode> in the \u003Ccode>lib\u003C\u002Fcode> array, they&#39;re typed automatically:\u003C\u002Fp>",{"id":176,"code":177,"type":87,"language":178,"highlight":179},"b31","function getCanonicalHost(input: string): string | null {\n  const url = URL.parse(input);\n  return url ? url.hostname : null;\n}","ts",[],{"id":181,"html":182,"type":72},"b32","\u003Cp>TypeScript knows \u003Ccode>URL.parse()\u003C\u002Fcode> returns \u003Ccode>URL | null\u003C\u002Fcode>, so the null check narrows the type to \u003Ccode>URL\u003C\u002Fcode> before you access its properties. No cast needed.\u003C\u002Fp>",{"id":184,"html":185,"text":185,"type":77,"level":31},"b33","Browser support",{"id":187,"type":154,"items":188,"ordered":18},"b34",[189,190],"\u003Ccode>URL.canParse()\u003C\u002Fcode>: \u003Cstrong>Baseline Widely Available\u003C\u002Fstrong> — Chrome 120 (January 2024), Firefox 115 (July 2023), Safari 17 (September 2023). Also available in Node.js 19.9 and Deno 1.33.","\u003Ccode>URL.parse()\u003C\u002Fcode>: \u003Cstrong>Baseline 2024\u003C\u002Fstrong> — Chrome 126 (June 2024), Firefox 126 (June 2024), Safari 18 (September 2024). Node.js 22.1.",{"id":192,"html":193,"type":72},"b35","\u003Cp>Both are safe for production in modern browser targets. For legacy support, the \u003Ccode>safeParseURL\u003C\u002Fcode> pattern above is still the correct polyfill — but with a clear feature-detect, you can retire it progressively.\u003C\u002Fp>",{"id":195,"html":196,"type":72},"b36","\u003C!-- quiz:start -->",{"id":198,"html":199,"text":199,"type":77,"level":31},"b37","🧠 Test yourself",{"id":201,"html":202,"type":72},"b38","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-08-01-url-parse-safe-parsing\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":204,"html":205,"type":72},"b39","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":207,"html":208,"type":72},"b40","\u003C!-- quiz:end -->",{"id":210,"html":211,"text":211,"type":77,"level":31},"b41","The takeaway",{"id":213,"html":214,"type":72},"b42","\u003Cp>Search your codebase for functions named \u003Ccode>safeParseURL\u003C\u002Fcode>, \u003Ccode>tryParseUrl\u003C\u002Fcode>, \u003Ccode>parseUrlSafely\u003C\u002Fcode>, or any utility with \u003Ccode>new URL()\u003C\u002Fcode> inside a try\u002Fcatch block. Each one can be replaced with a direct call to \u003Ccode>URL.parse()\u003C\u002Fcode>: same return type, same base-URL support, no wrapper to maintain, and no subtle behavioral difference between implementations written by different developers at different times.\u003C\u002Fp>",{"id":216,"html":217,"type":72},"b43","\u003Cp>Keep \u003Ccode>new URL()\u003C\u002Fcode> only where the input is guaranteed to be valid — URL construction from your own literals, config values, or template strings. Let the exception do its job there, and let \u003Ccode>URL.parse()\u003C\u002Fcode> handle the cases where invalid input is expected.\u003C\u002Fp>",{"id":219,"type":220},"b44","divider",{"id":222,"html":223,"type":72},"b45","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":225,"type":154,"items":226,"ordered":18},"b46",[227,228,229,230,231],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","Every codebase has this utility function somewhere. You wrote it the first time an untrusted URL caused an uncaught exception, or when a form field needed validation before submission, or when an API returned a path instead of a full URL. It's the try\u002Fcatch wrapper, and it works. The problem is that every team writes their own version, they differ in subtle ways, and they shouldn't need to exist. `URL.canParse()` and `URL.parse()` are the native replacements — shipping now in every modern browser.\n\n## The wrapper you've been maintaining\n\nThe `new URL()` constructor throws a `TypeError` on invalid input. That's correct when your input is a string literal you own — an exception is the right signal that your own code has a bug. It's the wrong behavior when you're parsing user input, a third-party API response, or a redirect URL from an HTTP header.\n\nThe result is this pattern:\n\n```js\nfunction safeParseURL(str, base) {\n  try {\n    return new URL(str, base);\n  } catch {\n    return null;\n  }\n}\n```\n\nA bad URL in a form field is not a programmer error. Exceptions used for expected control flow make call sites harder to read and suppress details that would help debugging. This wrapper should not need to exist.\n\n## `URL.canParse()` — validate without constructing\n\nWhen you only need to know whether a string is a valid URL — form validation, link display logic, redirect safety checks — `URL.canParse()` gives you a boolean with no allocation:\n\n```js\nURL.canParse('https:\u002F\u002Fexample.com')           \u002F\u002F true\nURL.canParse('not a url')                     \u002F\u002F false\nURL.canParse('javascript:alert(1)')           \u002F\u002F true — valid URL, unsafe redirect\nURL.canParse('\u002Fabout', 'https:\u002F\u002Fexample.com') \u002F\u002F true\nURL.canParse('::bad', 'https:\u002F\u002Fexample.com')  \u002F\u002F false\n```\n\nThe second argument is a base URL, resolved the same way `new URL()` resolves it. This matters when you're validating relative paths — from a `\u003Cbase>` tag, from API responses that return paths rather than full URLs, or from a `\u003Ca href>` value you want to check before following.\n\nOne important note: `URL.canParse()` validates the URL's *syntax*. It tells you whether the string can be parsed as a URL. It does not tell you whether the URL is safe to navigate to — `javascript:` and `data:` URLs are syntactically valid.\n\n## `URL.parse()` — the non-throwing constructor\n\n`URL.parse()` does what `new URL()` does but returns `null` instead of throwing when the input is invalid:\n\n```js\nconst url = URL.parse(userInput);\nif (!url) {\n  showError('Not a valid URL.');\n  return;\n}\n\u002F\u002F url is a URL instance — all properties and methods are available\nconsole.log(url.hostname);\nconsole.log(url.pathname);\nurl.searchParams.set('ref', 'footer');\n```\n\nEvery property and method available on a `URL` object — `hostname`, `pathname`, `searchParams`, `href` — works the same way. The only difference from `new URL()` is what happens on invalid input: null instead of a thrown exception.\n\nLike `URL.canParse()`, the second argument is a base URL:\n\n```js\nconst url = URL.parse('\u002Fproducts\u002F42', 'https:\u002F\u002Fexample.com');\n\u002F\u002F → URL { href: 'https:\u002F\u002Fexample.com\u002Fproducts\u002F42', pathname: '\u002Fproducts\u002F42', ... }\n\nconst relative = URL.parse('..\u002Fimages\u002Fphoto.jpg', 'https:\u002F\u002Fcdn.example.com\u002Fassets\u002Fv2\u002F');\n\u002F\u002F → URL { href: 'https:\u002F\u002Fcdn.example.com\u002Fassets\u002Fimages\u002Fphoto.jpg', ... }\n```\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-08-01-url-parse-safe-parsing\u002Fplayground\u002F)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Which to use and when\n\nThere are now three ways to get a URL object, and each has a clear use case:\n\n- **`new URL(str)`** — when the input is yours and invalid input is a bug. Use this for URL construction from values you control. The thrown exception surfaces real mistakes immediately.\n- **`URL.canParse(str)`** — when you only need validity, not the URL object. Form validation, display logic, redirect checking. Zero allocation — it doesn't construct anything.\n- **`URL.parse(str)`** — when you need the URL object and the input might be invalid. This replaces every `safeParseURL()` wrapper in your codebase.\n\nThe common antipattern to avoid: calling `URL.canParse()` immediately before `URL.parse()` or `new URL()`. That parses the string twice.\n\n```js\n\u002F\u002F Antipattern: double-parse\nif (URL.canParse(input)) {\n  const url = new URL(input); \u002F\u002F parsing the same string again\n  use(url);\n}\n\n\u002F\u002F Correct: parse once, check for null\nconst url = URL.parse(input);\nif (url) {\n  use(url);\n}\n```\n\nReach for `URL.canParse()` when null is the only thing you need to check — for example, gating a conditional that doesn't need the URL properties at all.\n\n## TypeScript support\n\nTypeScript added type signatures for both methods in **version 5.1** (May 2023). No extra `@types` package is needed — if your `tsconfig.json` includes `\"DOM\"` in the `lib` array, they're typed automatically:\n\n```ts\nfunction getCanonicalHost(input: string): string | null {\n  const url = URL.parse(input);\n  return url ? url.hostname : null;\n}\n```\n\nTypeScript knows `URL.parse()` returns `URL | null`, so the null check narrows the type to `URL` before you access its properties. No cast needed.\n\n## Browser support\n\n- `URL.canParse()`: **Baseline Widely Available** — Chrome 120 (January 2024), Firefox 115 (July 2023), Safari 17 (September 2023). Also available in Node.js 19.9 and Deno 1.33.\n- `URL.parse()`: **Baseline 2024** — Chrome 126 (June 2024), Firefox 126 (June 2024), Safari 18 (September 2024). Node.js 22.1.\n\nBoth are safe for production in modern browser targets. For legacy support, the `safeParseURL` pattern above is still the correct polyfill — but with a clear feature-detect, you can retire it progressively.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-08-01-url-parse-safe-parsing)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n## The takeaway\n\nSearch your codebase for functions named `safeParseURL`, `tryParseUrl`, `parseUrlSafely`, or any utility with `new URL()` inside a try\u002Fcatch block. Each one can be replaced with a direct call to `URL.parse()`: same return type, same base-URL support, no wrapper to maintain, and no subtle behavioral difference between implementations written by different developers at different times.\n\nKeep `new URL()` only where the input is guaranteed to be valid — URL construction from your own literals, config values, or template strings. Let the exception do its job there, and let `URL.parse()` handle the cases where invalid input is expected.\n\n---\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":234,"canonical":235,"description":236},"You've been wrapping `new URL()` in try\u002Fcatch. `URL.parse()` does it n","https:\u002F\u002Fbestpractic.org\u002Fblog\u002Furl-parse-safe-parsing","Validating an untrusted URL before parsing it used to mean a try\u002Fcatch wrapper or a regex. `URL.canParse()` checks validity without constructing. `URL.parse()` parses and returns n","019fe660-b8d7-7515-892c-c017e9255619",{"id":239,"locked":18},"019fe660-bf95-700e-b260-65579534228d",[241],{"id":242,"slug":34,"title":243,"_count":244},"019fe776-9ddd-746a-b9ce-ff46e56f6a7b","URL.canParse() and URL.parse() — safe URL parsing",{"questions":245},7,[247],{"locale":13,"slug":34},{"id":242,"slug":34,"title":243,"_count":249,"questionCount":245},{"questions":245},"\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">safeParseURL\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>str\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> base\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">try\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>str\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> base\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">catch\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">canParse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'https:\u002F\u002Fexample.com'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>           \u003Cspan class=\"shj-cmnt\">\u002F\u002F true\n\u003C\u002Fspan>\u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">canParse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'not a url'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>                     \u003Cspan class=\"shj-cmnt\">\u002F\u002F false\n\u003C\u002Fspan>\u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">canParse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'javascript:alert(1)'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>           \u003Cspan class=\"shj-cmnt\">\u002F\u002F true — valid URL, unsafe redirect\n\u003C\u002Fspan>\u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">canParse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'\u002Fabout'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'https:\u002F\u002Fexample.com'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F true\n\u003C\u002Fspan>\u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">canParse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'::bad'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'https:\u002F\u002Fexample.com'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>  \u003Cspan class=\"shj-cmnt\">\u002F\u002F false\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> url \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">parse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>userInput\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>url\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-func\">showError\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'Not a valid URL.'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F url is a URL instance — all properties and methods are available\n\u003C\u002Fspan>console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>url\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>hostname\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>url\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>pathname\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nurl\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>searchParams\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'ref'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'footer'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> url \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">parse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'\u002Fproducts\u002F42'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'https:\u002F\u002Fexample.com'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F → URL { href: 'https:\u002F\u002Fexample.com\u002Fproducts\u002F42', pathname: '\u002Fproducts\u002F42', ... }\n\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> relative \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">parse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'..\u002Fimages\u002Fphoto.jpg'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'https:\u002F\u002Fcdn.example.com\u002Fassets\u002Fv2\u002F'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F → URL { href: 'https:\u002F\u002Fcdn.example.com\u002Fassets\u002Fimages\u002Fphoto.jpg', ... }\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F Antipattern: double-parse\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">canParse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>input\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> url \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>input\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F parsing the same string again\n\u003C\u002Fspan>  \u003Cspan class=\"shj-func\">use\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>url\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Correct: parse once, check for null\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> url \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">parse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>input\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>url\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-func\">use\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>url\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">getCanonicalHost\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>input\u003Cspan class=\"shj-type\">: string\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-type\">: string\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> url \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">URL\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">parse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>input\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> url \u003Cspan class=\"shj-oper\">?\u003C\u002Fspan> url\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>hostname \u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>"]