# start_url 清单成员

> start_url 是已安装 PWA 从主屏幕或应用列表启动时打开的同源 URL，也是给启动打分析标记的位置，并在缺少 id 时参与应用身份的推导。

`start_url` 是用户点击图标时已安装应用打开的 URL，而不是安装那一刻恰好打开的页面。它相对清单 URL
解析，必须与清单同源，必须落在 `scope` 内；在没有 `id` 时，它还是应用身份的回退来源。

凡有安装路径的引擎都会读取它：Chrome 39、Edge 79、Samsung Internet 4.0、Android 上的 Firefox 79、
iOS 上的 Safari 11.3 与 macOS 上的 Safari 17（BCD `html.manifest.start_url`）。Chromium 还把有效的
`start_url` 作为可安装性的必要条件。

## 成员

- **类型**：字符串，按相对清单 URL 的 URL 解析。
- **默认值**：链接该清单的文档 URL。Chromium 丢弃跨源值时也回退到该文档 URL，并提示
  `property 'start_url' ignored, should be same origin as document.`（`manifest_parser.cc`）。
- **示例值**：`"/app/?utm_source=homescreen"`。

有两条规则与其他成员相互作用。如果 `start_url` 不在声明的 `scope` 内，Chromium 保留 `start_url`
而丢弃 `scope`，记录 `property 'scope' ignored. Start url should be within scope of scope URL.`，
应用随即静默得到默认作用域（清单 URL 所在目录）。另外，处理后的 `id` 默认取去掉查询串与片段的
`start_url`，所以在从未设置 `id` 的应用上改动 `start_url` 的路径，会产生第二个独立安装，而不是
更新第一个（Chrome for Developers，"Uniquely identify PWAs with the web app manifest id property"）。

启动 URL 是默认入口，不是唯一入口：通知、`shortcuts`、`share_target` 与 `file_handlers` 都会打开其他
作用域内的 URL，因此每条作用域内的路由都必须能作为冷启动页面工作。

:::observed
Chrome 155（Android 16，英文界面）的 `chrome://webapks` 页面：每个已安装的 WebAPK 在 **URI**、**Scope**、
**Manifest URL** 旁列出一行 **Manifest Start URL**，不用打开 DevTools 就能读到已安装应用将要启动的值。
桌面端的 DevTools > Application > Manifest 在 **Presentation** 一节以 **Start URL** 显示同一值，
不可达的值会出现在 **Installability** 下，提示 `Manifest start URL is not valid`。
:::

## 示例

示例使用同一份由 `https://app.example/manifest.webmanifest` 提供的清单。

### 解析到同一启动页的相对与绝对写法

相对值相对清单 URL 解析，而不是相对链接它的页面。位于 `/manifest.webmanifest` 的清单写
`"start_url": "app/"`，启动的是 `https://app.example/app/`，与绝对写法相同。

```json
{
  "start_url": "app/",
  "scope": "/app/"
}
```

绝对写法 `"https://app.example/app/"` 等价；`"https://cdn.example/app/"` 这类其他源上的值会被丢弃，
由文档 URL 顶替。

### 给启动打分析标记并在启动时读取

没有专用 API 的情况下，`start_url` 上的查询串是区分已安装应用启动与浏览器访问的唯一手段。脚本读一次参数、
上报，然后删掉它，应用内链接就不会再带着它；参数不存在时什么也不发。

```json
{
  "start_url": "/app/?utm_source=homescreen&utm_medium=pwa"
}
```

```js
const params = new URLSearchParams(location.search);
if (params.get('utm_source') === 'homescreen') {
  navigator.sendBeacon('/analytics', JSON.stringify({ launch: 'installed-app' }));
  params.delete('utm_source');
  params.delete('utm_medium');
  history.replaceState(history.state, '', `${location.pathname}${params.size ? `?${params}` : ''}`);
}
```

查询串属于 Chromium 检查清单更新时比对的内容之一（web.dev，"How Chrome handles updates to the web
app manifest"），所以之后改动标记会在下一次清单更新检查时生效，而不是下一次启动。

### 迁移启动页而不产生第二个安装

在已上线应用上改动 `start_url` 之前，先把 `id` 固定为浏览器已经推导出的值（去掉查询串的旧
`start_url`）。有了 `id`，新的 `start_url` 作为更新应用；没有它，Chromium 会把清单当作另一个应用。

```json
{
  "id": "/app/",
  "start_url": "/app/home/?utm_source=homescreen",
  "scope": "/app/"
}
```

`id` 一旦设定就不必再改，之后 `start_url` 怎么变都不影响。

## 另请参阅

- [id 清单成员](/zh/reference/manifest/id/)
- [scope 清单成员](/zh/reference/manifest/scope/)
- [已安装的 PWA 如何获取清单更新](/zh/reference/manifest/manifest-updates/)
- [可安装性条件：PWA 如何才能被安装](/zh/reference/installation/installability-criteria/)
- [Web Application Manifest: start_url member](https://www.w3.org/TR/appmanifest/#start_url-member)（w3.org）
- [Uniquely identify PWAs with the web app manifest id property](https://developer.chrome.com/docs/capabilities/pwa-manifest-id)（developer.chrome.com）