使用 URLPattern() 构建你自己的路由器
Build Your Own Router with URLPattern()

原始链接: https://jschof.dev/posts/2025/11/build-your-own-router/

## 使用 URLPattern 的原生 JavaScript SPA 路由器 本文详细介绍了如何仅使用原生 JavaScript 和新推出的 `URLPattern` API 构建一个简单的单页面应用程序 (SPA) 路由器。路由的核心挑战不是条件渲染,而是准确地将浏览器 URL 匹配到相应的组件——特别是那些具有动态片段的 URL(例如 `/posts/{post_id}`)。`URLPattern` 简化了此匹配过程。 该方法涉及一个组件,它接收路由器配置(一个将 URL 映射到 Web 组件的数组),并根据当前 URL 渲染适当的组件。处理导航需要拦截链接点击 (`preventDefault()`) 并使用 `window.pushState` 手动更新 URL,以模拟页面过渡。至关重要的是,需要服务器配置(例如 Vite 的 `spa` 模式或 Netlify 重定向),以确保所有路由都提供主 HTML 文件。 浏览器 `popstate` 事件处理后退/前进按钮导航,根据更新的 URL 触发重新渲染。作者提供了一个可运行的 StackBlitz 示例,并提醒注意安全漏洞——特别是避免基于 URL 参数动态注册组件,以防止潜在的 XSS 攻击。最后,文章承认从头开始构建路由器很复杂,并建议考虑使用成熟的框架,同时强调理解底层原生 API 的教育价值。

黑客新闻 新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 使用 URLPattern() 构建你自己的路由器 (jschof.dev) 10 分,by tobr 2 小时前 | 隐藏 | 过去 | 收藏 | 讨论 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Published 2025-11-11, About 8 minute read.

URLPattern just became available in all browsers:

So I wanted to dig into what it would take to make a simple SPA router with vanilla JavaScript and browser APIs. We should be able to make a component that takes in a router configuration and renders the appropriate component determined by the browser URL.

What does URLPattern() Do? 📎

Conditionally rendering components is not the difficult part with routers. The difficult part is accurately testing the browser URL to determine which component should render. And not only that, we have to be able to capture dynamic parts of routes (think something like /posts/{post_id}.)

So, without further ado, here are some examples showing how to test if a route URL matches a pattern! You can then use this mechanism to create a router with easily configured paths.

You might be surprised by the fourth example above. There is a distinction between /cat and /cat/. So to handle this, you can make the pattern optionally include an ending forward slash in a group with curly brackets and mark it optional with ?:

Yet another surprise! You may expect that you can accept more after /cat/. To do that, include a wildcard asterisk:

Where do we start? 📎

I'm going to use an array of config objects that tie a URL route to specific web components. It's very similar to how you can create routers with vue-router.

The order of the config objects matters. We'll be testing each pattern one by one, and if we find a match, we'll render that web component.

How do we do the rendering? That would be the job of the web component we're placing all this logic in. The component will look at the current window URL, test that against all the router configs that we set up with URLPattern, and create and render the appropriate web component as a child.

Some frameworks call this router the "outlet" component.

Of course, you will need to have the web components my-home, my-posts, and my-about registered as well.

But there we have it - a router that will render the appropriate web component on page load. We have much work to do, though. What if someone clicks a link? What if someone uses the browser to navigate forward or backward? We need to handle those, and luckily it's not too difficult.

One thing to realize is that if you navigate to http://www.myblog.com/some/path the server is going to normally try to resolve /some/path on the back end. It might actually look for the folders "some" and "path". But in a SPA we don't have folders- we have one index HTML file that handles virtual paths. It's all done in the client in JS! No matter which path we're going to, the server really only has to serve up the index page. The client will then take over, use our new URLPattern and handle rendering the appropriate component.

For configuring Vite to do this, it's dead simple. You can use a configuration called spa. Just update your vite config:

This works well for your Vite local server. Unfortunately it's going to depend on where you deploy and what other frameworks/dev servers you're using. For some places like netlify you'll need to set a redirect rule in your netlify config. You may have to consult Stack Overflow, Google, or your LLM of choice to figure out how to do this for your particular situation.

But once you've got your server configuration and redirects in place, we can then start handling clicks!

We want to basically coopt any link clicks, and stop the browser from navigating normally. This means we preventDefault() all click events, and pull the target of the link out to test against our URL patterns. This lets us know which component to render, and we set the URL manually to be what the anchor tag was pointing to. It looks like we're changing pages, but really we're simulating a page transition.

We need to set up a click handler when our router component connects to the DOM:

Of course, make sure you tear down these handlers in the disconnectedCallback!

The user sees the URL change, the page transition, and even a new entry in the browser navigation history when a user clicks on a link. Now, we need to make sure that the browser doesn't actually go back or forward, but hooks into our router when back or forward buttons are clicked.

One last detail: browser navigation 📎

When a browser goes back or forward either programmatically (with window.back()/forward()) or from a user clicking the back/forward buttons, an event gets emitted: popstate.

The nice thing about this event is that the browser already moves back/forward to the entry that we've been pushing to the history stack when we used window.pushState. In other words, we just have to listen when this back/forward navigation happens, and render our component. After back/forward is pressed, the URL is already updated

So here is the final piece for our minimal viable router:

A working example 📎

If you're interested in seeing it work, here's an example built out in StackBlitz.

There's more to do! Here is the list of things I would suggest investigating:

Things to keep in mind... 📎

We are working pretty low-level. Making your own router renderer like this can expose you to XSS if you leave open ways for people to mess with your router config. For example, if you let people set the configuration array directly on the router component, someone could come along and register their own web component in the console, and navigate to their component to render it. Effectively, you would be allowing other parties to run their code in your router!

This is why, at least in the example being discussed here, the router configuration is defined in a private variable in our router component. If we exposed this property publicly, someone could register their own web component and have our router render it.

How do we solve this? I would say never render based purely off the query params or dynamic segments of a URL. Always keep the actual rendering web components in a static list- probably a list that's in a private variable on the router.

Another thing- should we be building a router with web components? Eh... maybe not? Lit seems to think it would be helpful and useful. But there are a lot of considerations you have to handle with your own implementation that framework routers have already solved. Web components also add another level of security you need to be aware of.

I think it's worth investigating and learning from. And it's also nice to understand the native APIs that continue to come out and make our lives easier as we lean into the platform.

Until next time.

Find me on Bluesky or Mastodon. I also have an RSS feed here

联系我们 contact @ memedata.com