Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .changeset/seo-severity-alignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
'@svelte-vitals/core': minor
---

The CLI's default gate is `--fail-on critical`: a project failing only on a missing
`<meta name="description">` now exits `0` instead of `1` (a deliberate loosening). Three more gate
movements only surface under a non-default `--fail-on warning` (or an equivalent `--min-health`):
a project failing only on a missing `og:url` now exits **`1` instead of `0` — a tightening, and the
only one of the four that can turn a previously-green CI red on upgrade.** A project failing only
on a missing `og:description` now exits `0` instead of `1`. A project failing only on a
multi-`<h1>` page now also exits `0` instead of `1`. No other rule's severity changed, so no other
gate behavior changes. Realigned four SEO rule severities with their underlying evidence,
following the 2026-08-09 rule-validity review's Priority-2 findings.

Two of the four changes shrink the `seo::route` scoring pair's total weight from 110 to 100
(description-presence −10, og-url +4, og-description −4, net −10) — so a project's SEO/Health
score can shift by a point or two even with no finding changes at all, simply because the
denominator moved. `single-h1` and `hreflang` don't affect the pair's weight (see below).

- `seo/description-presence`: `critical` → `warning`. `critical` now uniformly means
deploy-blocking (the four crash/security rules — `correctness/orphan-effect`,
`correctness/orphan-lifecycle`, `correctness/server-browser-global`,
`security/handler-state-write` — plus `seo/title-presence`) — Google only "sometimes" uses the
provided meta description for the search snippet, generating one from page content the rest of
the time, so a missing one is real but non-blocking.
- `seo/og-url`: `info` → `warning`, and `seo/og-description`: `warning` → `info` — swapped to match
the [Open Graph protocol](https://ogp.me/)'s own required/optional split (`og:url` is Basic/required
metadata; `og:description` is Optional). The previous ordering's "og:url is covered by canonical"
rationale conflated two different jobs (canonical targets search engines, og:url targets social
platforms) and is now recorded as historical context in the `og:url` docs page.
- `seo/single-h1`: severity split — a page with zero `<h1>` stays `warning` (a real primary-heading
gap); two or more `<h1>` is demoted to `info` (a single `<h1>` is the conventional signal, but no
official source documents a ranking penalty for several, so it's a style nit, not a defect). The
rule's own registered severity (read by scoring's inventory and the rules index) stays `warning`
— only the per-finding severity is split, so the `seo::route` pair's total weight is unaffected.
A global `rules: { 'seo/single-h1': <severity> }` override flattens both arms back to one
severity, same as any other rule.
- `seo/hreflang`: wording only, severity unchanged (`warning`). The "2+ alternates without
x-default" message and recommendation no longer imply a defect — Google's guidance frames
`x-default` as something to "consider," specifically for language-selector or auto-redirecting
pages, not a requirement for every multilingual site. The malformed-hreflang-code arm is
unchanged.
8 changes: 4 additions & 4 deletions docs/src/content/docs/ja/rules/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ svelte-vitals が報告するルールをカテゴリ別にまとめました。

| ルール | 重大度 | 概要 |
| ---------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ |
| [`seo/description-presence`](/ja/rules/seo/description-presence) | 🔴 critical | すべてのルートに &lt;meta name="description"&gt; が必要です。 |
| [`seo/title-presence`](/ja/rules/seo/title-presence) | 🔴 critical | どのルートでも &lt;title&gt; が空にならないようにしましょう。 |
| [`seo/canonical-url`](/ja/rules/seo/canonical-url) | 🟡 warning | すべてのルートに &lt;link rel="canonical"&gt; タグを含めるべきです。 |
| [`seo/charset`](/ja/rules/seo/charset) | 🟡 warning | 文字エンコーディングは &lt;meta charset&gt; で宣言します。 |
| [`seo/description-presence`](/ja/rules/seo/description-presence) | 🟡 warning | すべてのルートに &lt;meta name="description"&gt; が必要です。 |
| [`seo/duplicate-description`](/ja/rules/seo/duplicate-description) | 🟡 warning | meta description はルートごとに違う内容にしましょう。 |
| [`seo/duplicate-title`](/ja/rules/seo/duplicate-title) | 🟡 warning | &lt;title&gt; はルートごとに違う内容にしましょう。 |
| [`seo/hreflang`](/ja/rules/seo/hreflang) | 🟡 warning | hreflang の alternate には有効なコードを使い、x-default も宣言しましょう。 |
| [`seo/hreflang`](/ja/rules/seo/hreflang) | 🟡 warning | hreflang の alternate には有効なコードを使いましょう。x-default はセレクター・リダイレクトページで推奨されます。 |
| [`seo/html-lang`](/ja/rules/seo/html-lang) | 🟡 warning | app.html の &lt;html&gt; に lang 属性を設定しましょう。 |
| [`seo/image-alt`](/ja/rules/seo/image-alt) | 🟡 warning | &lt;img&gt; には alt 属性を付けます。 |
| [`seo/json-ld-relative-url`](/ja/rules/seo/json-ld-relative-url) | 🟡 warning | JSON-LD の URL は絶対 URL で書きます。 |
| [`seo/json-ld-required-props`](/ja/rules/seo/json-ld-required-props) | 🟡 warning | 認識できる @type には、リッチリザルトに必要なプロパティを揃えましょう。 |
| [`seo/json-ld-validity`](/ja/rules/seo/json-ld-validity) | 🟡 warning | ページの JSON-LD は、@context と @type を備えた妥当な JSON である必要があります。 |
| [`seo/og-description`](/ja/rules/seo/og-description) | 🟡 warning | og:description はどのルートにも入れましょう。 |
| [`seo/og-image`](/ja/rules/seo/og-image) | 🟡 warning | &lt;meta property="og:image"&gt; タグはどのルートにも必要です。 |
| [`seo/og-title`](/ja/rules/seo/og-title) | 🟡 warning | &lt;meta property="og:title"&gt; タグはどのルートにも必要です。 |
| [`seo/og-url`](/ja/rules/seo/og-url) | 🟡 warning | og:url には、そのページの正規アドレスを入れましょう。 |
| [`seo/robots-txt`](/ja/rules/seo/robots-txt) | 🟡 warning | プロジェクトに robots.txt を用意しましょう。 |
| [`seo/single-h1`](/ja/rules/seo/single-h1) | 🟡 warning | &lt;h1&gt; は 1 ページにつき 1 つだけにしましょう。 |
| [`seo/sitemap-xml`](/ja/rules/seo/sitemap-xml) | 🟡 warning | プロジェクトに sitemap.xml を用意しましょう。 |
Expand All @@ -59,7 +59,7 @@ svelte-vitals が報告するルールをカテゴリ別にまとめました。
| [`seo/json-ld-date-format`](/ja/rules/seo/json-ld-date-format) | 🔵 info | JSON-LD の日付プロパティは ISO-8601 形式で書きます。 |
| [`seo/json-ld-deprecated-type`](/ja/rules/seo/json-ld-deprecated-type) | 🔵 info | 一部のスキーマ型は、Google のリッチリザルトが廃止または制限されました。 |
| [`seo/json-ld-placeholder`](/ja/rules/seo/json-ld-placeholder) | 🔵 info | JSON-LD にプレースホルダのまま残っている値がないか確認します。 |
| [`seo/og-url`](/ja/rules/seo/og-url) | 🔵 info | og:url には、そのページの正規アドレスを入れましょう。 |
| [`seo/og-description`](/ja/rules/seo/og-description) | 🔵 info | og:description はどのルートにも入れましょう。 |
| [`seo/sitemap-in-robots`](/ja/rules/seo/sitemap-in-robots) | 🔵 info | robots.txt から sitemap の場所をクローラーに伝えましょう。 |
| [`seo/title-length`](/ja/rules/seo/title-length) | 🔵 info | &lt;title&gt; は 30〜60 文字に収めましょう。 |
| [`seo/twitter-card`](/ja/rules/seo/twitter-card) | 🔵 info | X/Twitter で大きなカード表示にするため、twitter:card を宣言しましょう。 |
Expand Down
4 changes: 3 additions & 1 deletion docs/src/content/docs/ja/rules/seo/description-presence.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: seo/description-presence · ディスクリプションの存在
description: すべてのルートに <meta name="description"> が必要です。
---

**重大度:** critical
**重大度:** warning

## チェック内容

Expand All @@ -13,6 +13,8 @@ description: すべてのルートに <meta name="description"> が必要です

メタディスクリプションは、検索エンジンがタイトルの下に表示するスニペットです。設定していないと検索エンジンがページ本文から自動生成しますが、質の低いスニペットになりがちです。

2026-08-09 の重大度レビュー以前は `critical` でした。`critical` は現在、デプロイをブロックすべき事象(クラッシュ、セキュリティの漏えい)、または SEO では検索エンジンが常に必要とする唯一のシグナル(`seo/title-presence`)に限定しています。ディスクリプションは事情が異なります。Google は提供されたディスクリプションを検索結果のスニペットに使うのは「場合による」と明言しており、それ以外はページ本文から自動生成します。つまりディスクリプションの欠如は実害のある問題ではあっても、デプロイを止めるべきものではありません。そのため `seo/og-image` や `seo/canonical-url` と同じ `warning` としています。

## 修正方法

`<svelte:head>` 内に `<meta name="description">` を追加するか、メタコンポーネントのディスクリプションを設定します:
Expand Down
14 changes: 10 additions & 4 deletions docs/src/content/docs/ja/rules/seo/hreflang.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: seo/hreflang · hreflang の妥当性
description: hreflang の alternate には有効なコードを使い、x-default も宣言しましょう。
description: hreflang の alternate には有効なコードを使いましょう。x-default はセレクター・リダイレクトページで推奨されます。
---

**重大度:** warning
Expand All @@ -9,12 +9,14 @@ description: hreflang の alternate には有効なコードを使い、x-defaul

`<link rel="alternate" hreflang="…">` の alternate を検証します。本ルールはオプトインで、hreflang alternate のないページは検出しません。alternate が存在する場合、次を検出します。

- 不正な `hreflang` 値(`x-default` でも、`en`、`en-US`、`zh-Hant`、`es-419` のような整形式の BCP-47 コードでもないもの)
- alternate が 2 つ以上あるのに `x-default` がない場合
- 不正な `hreflang` 値(`x-default` でも、`en`、`en-US`、`zh-Hant`、`es-419` のような言語(-スクリプト)(-地域)形式のコードでもないもの)
- alternate が 2 つ以上あるのに `x-default` が宣言されていない場合

## なぜ重要か

hreflang コードが不正だったり `x-default` が欠けていたりすると国際ターゲティングが機能せず、検索エンジンが誤った言語版を表示したり、アノテーション自体を無視したりします。
hreflang コードが不正だと国際ターゲティングが機能せず、検索エンジンが誤った言語版を表示したり、アノテーション自体を無視したりします。

一方、`x-default` の欠如は性質が異なります。[Google 自身のガイダンス](https://developers.google.com/search/docs/specialty/international/localized-versions)は「特に言語・国選択ページや自動リダイレクトするホームページでは、一致する言語がなかったユーザー向けのフォールバックページの追加を検討してください」と述べており、これは特定の形のページに向けた推奨であって、多言語サイト全般に対する欠陥ではありません。言語セレクターも自動リダイレクトも持たず、固定の言語 alternate 一覧だけを提示するページには、フォールバック先が必要な「一致しない言語のユーザー」自体が存在しないため、そこで `x-default` を省略するのは見落としではなく妥当な選択です。
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## 修正方法

Expand All @@ -25,3 +27,7 @@ hreflang コードが不正だったり `x-default` が欠けていたりする
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
</svelte:head>
```

## 制限事項

検証対象は BCP-47 の実用的なサブセット(言語・任意のスクリプト・任意の地域)のみで、文法全体はカバーしません。BCP-47 のバリアントや拡張(例: `de-DE-1996`、`en-US-u-hc-h12`)は有効な hreflang 値ですが、本ルールでは認識されず不正な値として検出されます。
Loading