seo-lint-next 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 914c840: Avoid false positives for links whose accessible text is provided by dynamic JSX expressions.
8
+
3
9
  ## 0.2.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  <img alt="license" src="https://img.shields.io/badge/license-MIT-111827">
16
16
  </p>
17
17
 
18
- `seo-lint-next` catches the metadata bugs that usually escape to production: missing titles, broken canonicals, unsafe `metadataBase`, bad OG tags, accidental `noindex`, sitemap gaps, invalid JSON-LD, heading skips, and weak image alt text.
18
+ `seo-lint-next` catches the SEO bugs that usually escape to production: missing titles, broken canonicals, unsafe `metadataBase`, bad OG tags, accidental `noindex`, sitemap gaps, invalid JSON-LD, heading skips, weak image alt text, broken locale metadata, bad App Router patterns, and crawl-blocking config.
19
19
 
20
20
  ## Install
21
21
 
@@ -45,18 +45,33 @@ npx seo-lint-next --strict
45
45
 
46
46
  ## Rule Set
47
47
 
48
- | Rule | Default | Catches |
49
- | ----------------------------- | ------: | ------------------------------------------------------------------- |
50
- | `no-missing-title` | error | Missing, empty, duplicate, or weak dynamic titles |
51
- | `no-missing-metadata-base` | error | Missing, localhost, or non-HTTPS root `metadataBase` |
52
- | `no-missing-description` | warn | Missing, duplicate, too short, or too long descriptions |
53
- | `no-missing-canonical` | error | Missing canonicals and hardcoded dynamic-route canonicals |
54
- | `no-missing-og-tags` | warn | Missing Open Graph/Twitter tags and weak image dimensions |
55
- | `no-broken-heading-hierarchy` | warn | Missing/multiple H1s and skipped heading levels |
56
- | `no-img-missing-alt` | warn | Raw `<img>`, missing/generic alt text, layout-shifting images |
57
- | `no-accidental-noindex` | error | Public `noindex`, blanket robots blocks, risky X-Robots headers |
58
- | `no-missing-sitemap` | warn | Missing sitemap, relative URLs, missing robots sitemap reference |
59
- | `no-invalid-json-ld` | error | Invalid JSON-LD syntax, bad context, missing required schema fields |
48
+ | Rule | Default | Catches |
49
+ | ---------------------------------------- | ------: | ---------------------------------------------------------------- |
50
+ | `no-missing-title` | error | Missing, empty, duplicate, or weak dynamic titles |
51
+ | `no-missing-metadata-base` | error | Missing, localhost, or non-HTTPS root `metadataBase` |
52
+ | `no-missing-description` | warn | Missing, duplicate, too short, or too long descriptions |
53
+ | `no-missing-canonical` | error | Missing canonicals and hardcoded dynamic-route canonicals |
54
+ | `no-missing-og-tags` | warn | Missing Open Graph/Twitter tags |
55
+ | `no-broken-heading-hierarchy` | warn | Missing/multiple H1s and skipped heading levels |
56
+ | `no-img-missing-alt` | warn | Raw `<img>`, missing/generic alt text, layout-shifting images |
57
+ | `no-accidental-noindex` | error | Public `noindex`, blanket robots blocks, risky X-Robots headers |
58
+ | `no-missing-sitemap` | warn | Missing sitemap, relative URLs, missing robots sitemap reference |
59
+ | `no-invalid-json-ld` | error | Invalid JSON-LD syntax, bad context, missing schema fields |
60
+ | `no-missing-lang-attribute` | error | Missing or invalid `<html lang>` in App Router layouts |
61
+ | `no-missing-viewport` | error | Broken viewport overrides and disabled user zoom |
62
+ | `no-use-client-on-page` | warn | Top-level client pages and ignored metadata exports |
63
+ | `no-missing-hreflang` | warn | Locale routes without `alternates.languages` |
64
+ | `no-generic-anchor-text` | warn | Empty or generic link text |
65
+ | `no-missing-404-page` | warn | Missing or weak `app/not-found.tsx` recovery pages |
66
+ | `no-missing-og-image-dimensions` | warn | OG images without width, height, or alt text |
67
+ | `no-blocking-next-static` | error | Robots rules blocking `/_next/` rendering assets |
68
+ | `no-missing-breadcrumb-schema` | warn | Deep pages without `BreadcrumbList` JSON-LD |
69
+ | `no-title-in-pages-head` | error | `next/head` metadata patterns inside `app/` |
70
+ | `no-dynamic-import-ssr-false-on-content` | warn | `dynamic(..., { ssr: false })` around likely content |
71
+ | `no-missing-next-font` | warn | External Google Fonts instead of `next/font` |
72
+ | `no-redirect-chain-in-next-config` | warn | Redirect chains and temporary redirects in `next.config` |
73
+ | `no-missing-article-dates` | warn | Article pages missing freshness metadata |
74
+ | `no-missing-error-boundary-metadata` | warn | Missing error boundaries or weak recovery links |
60
75
 
61
76
  ## Configuration
62
77
 
@@ -84,4 +99,4 @@ export default [
84
99
  - run: npm run check
85
100
  ```
86
101
 
87
- The package has no production bundle impact. It statically inspects App Router source files, JSX, `metadata` exports, `generateMetadata()` returns, `app/robots.ts`, `public/robots.txt`, and `app/sitemap.ts`.
102
+ The package has no production bundle impact. It statically inspects App Router source files, JSX, `metadata` exports, `generateMetadata()` returns, `next.config.*`, `app/robots.ts`, `public/robots.txt`, and `app/sitemap.ts`.
package/dist/cli.cjs CHANGED
@@ -618,7 +618,9 @@ var noGenericAnchorText = createRule(
618
618
  if (name !== "a" && name !== "Link") return;
619
619
  const text = jsxChildText(node).toLowerCase();
620
620
  const label = jsxAttributeString(node.openingElement, "aria-label");
621
+ const hasDynamicText = hasDynamicTextContent(node);
621
622
  if (!text && !label) {
623
+ if (hasDynamicText) return;
622
624
  const hasImage = hasChildElement(node, "img") || hasChildElement(node, "Image");
623
625
  report(
624
626
  context,
@@ -642,6 +644,16 @@ var noGenericAnchorText = createRule(
642
644
  }),
643
645
  "suggestion"
644
646
  );
647
+ function hasDynamicTextContent(node) {
648
+ return (node.children ?? []).some((child) => {
649
+ if (child.type === "JSXExpressionContainer") {
650
+ const expression = child.expression;
651
+ return expression?.type !== "JSXEmptyExpression";
652
+ }
653
+ if (child.type === "JSXElement") return hasDynamicTextContent(child);
654
+ return false;
655
+ });
656
+ }
645
657
 
646
658
  // src/rules/no-img-missing-alt.ts
647
659
  var meaningless = /* @__PURE__ */ new Set(["image", "img", "photo", "picture", "screenshot"]);