docs: body link utilities and the app-without-pages note
- autolink, applyOutbound and imageAttrs: the body link rules a content layer runs at SSR on sanitized HTML (UI-SPEC L7) - htmlSitemap: an app without pages sets false, since the sitemap pages would turn the router on - the purge route's copies in the journal, menu and FAQ layers are gone
This commit is contained in:
@@ -30,7 +30,7 @@ The layer's only module is its own local one (`lib/seo-module.ts`); it pulls in
|
||||
- **Robots and staging.** A `staging` answer (the backend outside `SUMMER_ENV=production`) makes every page `noindex, nofollow`, known or not. The `X-Robots-Tag` response header follows the same value, or the page's `x_robots_tag`; a redirect carries none.
|
||||
- **404 log.** Each page view answered 404 (a GET that accepts `text/html`, from an unknown route or a page that throws a 404) is reported once with `POST <apiBase>/notfound`, `{"url", "referer"}` and `Authorization: Bearer <notfoundToken>`, after the response, so it never delays it. Without a token nothing is reported.
|
||||
- **Root files.** With a backend that answers `GET <apiBase>/site` (v0.2.0), `/robots.txt`, `/sitemap.xml`, `/llms.txt` and `/sitemap-<name>.xml` (served from the backend's `/_sitemap/files/`) are proxied to `backendBase`, and so is each one-segment `.html`, `.txt` or `.xml` file the site answer lists in `root_files`: a verification file (`<apiBase>/verification/<name>`) or the IndexNow key file (`/_sitemap/indexnow/<key>.txt`). The backend's status, content type and `X-Robots-Tag` are passed on, with `X-Content-Type-Options: nosniff`; a backend that does not answer or answers 5xx gives 503 with `Retry-After: 60`, so crawlers retry instead of reading a missing robots.txt as "everything allowed". Any other path passes through to the app. The site answer is cached for 300 seconds and cleared by the purge; while the backend is down the last one seen is used. With a backend whose `/site` answers 404 (before v0.2.0, or no SEO plugin), or one not reached since the server started, robots.txt and the sitemaps are not proxied (route them to the backend in the site's proxy) and verification files keep the v0.1.1 lookup: a request for a single-segment `.html`, `.txt` or `.xml` file (`/google0123.html`, `/BingSiteAuth.xml`) is looked up under `GET <apiBase>/verification/<name>` and served with `X-Content-Type-Options: nosniff`, a sandboxing CSP and `X-Robots-Tag: noindex` when the plugin has it; otherwise it falls through to the app's routes. `robots.txt`, `sitemap.xml`, `sitemap.xsl`, `rss.xml`, `feed.xml`, `atom.xml` and `llms.txt` are never looked up that way. `rootFiles: false` turns both off.
|
||||
- **Site cache purge (v0.2.0).** `POST /api/__site__/cache/purge` (moved here from vue-journal-layer, vue-menu-layer and vue-faq-layer, same contract) removes every Nitro route-cache entry and empties the layer's resolve and site caches, so an admin save shows on the next page view. The backend calls it on loopback after the save commits, with `Authorization: Bearer <cachePurge.token>` and `{"kind","slug"}`; the answer is `{"purged": <n>}`. It answers 404 without a token of at least 32 characters and for any proxied request (X-Forwarded-For, X-Real-IP or Forwarded) unless `cachePurge.allowProxied` is true, and 401 for a missing or wrong bearer. `/api/__site__/**` is never route-cached. While another layer still ships its own copy of the route and that copy answers, `server/plugins/seo-cache-purge.ts` empties the SEO caches after any 200 purge (also after the journal's deprecated `/api/__journal__/cache/purge`).
|
||||
- **Site cache purge (v0.2.0).** `POST /api/__site__/cache/purge` (moved here from vue-journal-layer, vue-menu-layer and vue-faq-layer, which no longer ship a copy since they extend this layer; same contract) removes every Nitro route-cache entry and empties the layer's resolve and site caches, so an admin save shows on the next page view. The backend calls it on loopback after the save commits, with `Authorization: Bearer <cachePurge.token>` and `{"kind","slug"}`; the answer is `{"purged": <n>}`. It answers 404 without a token of at least 32 characters and for any proxied request (X-Forwarded-For, X-Real-IP or Forwarded) unless `cachePurge.allowProxied` is true, and 401 for a missing or wrong bearer. `/api/__site__/**` is never route-cached. While another layer still ships its own copy of the route and that copy answers, `server/plugins/seo-cache-purge.ts` empties the SEO caches after any 200 purge (also after the journal's deprecated `/api/__journal__/cache/purge`).
|
||||
- **Build guard (v0.2.0, D-70).** An indexable page must render on the server. `nuxt build` (and `nuxt generate`) fails with `[vue-seo-layer] indexable routes must render on the server (D-70): <routes>` when a route rule sets `ssr: false`, a page is a `.client.vue` page or the app sets `ssr: false`, unless the route is declared in `seo.noindexRoutes` (or the app sets `seo.indexable: false`). The guard reads the rules after every layer and module merged them: `routeRules`, `nitro.routeRules` and the rules of each layer; `experimental.inlineRouteRules` is refused, since `defineRouteRules` in a page could turn server rendering off where the guard cannot see it. `nuxt dev` and `nuxt prepare` only warn.
|
||||
- **Noindex routes (v0.2.0, D-23).** A route-rule pattern in `seo.noindexRoutes` (`'/p/**'`, `'/konto'`, `'/x/*'`) gets `X-Robots-Tag: noindex, nofollow` through its route rule (unless the app set its own header there) and a `robots` meta tag. A page whose meta sets `requiresAuth: true` gets `noindex, nofollow`, and one whose meta sets `seo: { robots: '…' }` gets that value, as meta tag and, during SSR, header, without any SEO code in the page; this is how apps mark signed-in and auth pages. These values win over the CMS's, except on staging. No other page is ever marked noindex by the layer.
|
||||
|
||||
@@ -60,7 +60,7 @@ Module options under the `seo` key of the app's `nuxt.config.ts` (v0.2.0). They
|
||||
| `indexable` | `true` | `false` for a site that is never indexed: the build guard checks nothing. |
|
||||
| `routeReport` | `true` | Report the static routes to the plugin's page registry at server start (needs `seo.routeReportToken` or `seo.notfoundToken`). |
|
||||
| `pagination` | `{ form: 'query', param: 'page', segments: { pl: 'strona', en: 'page' } }` | The pagination URL form the site is built for: `query` (`/blog?page=2`) or `path` (`/blog/strona/2`, the segment per locale). The resolve and site answers win at run time; the drift check logs a difference. |
|
||||
| `htmlSitemap` | `{ paths: { pl: '/mapa-strony', en: '/sitemap' } }` | The HTML sitemap pages by locale (full paths, overview at the path, groups at `<path>/<group>`); `false` registers none. A path an app page already has stays the app's. |
|
||||
| `htmlSitemap` | `{ paths: { pl: '/mapa-strony', en: '/sitemap' } }` | The HTML sitemap pages by locale (full paths, overview at the path, groups at `<path>/<group>`); `false` registers none. A path an app page already has stays the app's. An app without pages of its own (only `app.vue`) sets `false`: the sitemap pages would be its only routes, which turns Nuxt's router on and makes every other path answer 404. |
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -102,6 +102,7 @@ The layer reads the answer of `GET /_seo/api/v1/resolve` (`app/utils/types.ts`):
|
||||
- `useSeoSite()`: the site answer through `GET /api/__seo__/site` (`{"site": … | null}`, cached on the server), shared under one key on the server and in the browser; `data.value` is `null` with the layer off or an older or unreachable backend (v0.2.0).
|
||||
- `usePagination(options)`, `useSeoPages()`: see Components (v0.2.0).
|
||||
- `seoPaginationWindow(current, last)`, `seoPageHref(url, page, form, segment, param)`, `seoPageNumber(url, form, segment, param)`, `seoClaimBreadcrumbs(state, path, id)`, `seoReleaseBreadcrumbs(state, id)`, `seoSitePath(url, ...origins)`, `seoFillPages(head, pages)`: pure helpers behind the components (v0.2.0; prefixed because `app/utils` is auto-imported next to other layers' helpers).
|
||||
- `autolink(html, links, max, self)`, `applyOutbound(html, { siteHost, newTab, nofollowDomains, sponsoredDomains, hint })`, `imageAttrs(html)` (`app/utils/bodyLinks.ts`): the body link rules a content layer runs at SSR on sanitized HTML, in this order (UI-SPEC L7; vue-journal-layer's post body does). `autolink` links each phrase of the resolve answer's `autolinks` at its first occurrence, as a whole word and case-insensitive, at most `max` (`autolink_max`) in the whole text, never inside `a`, `h1`–`h6`, `code`, `pre`, `figcaption` or `button` and never to the page itself (`self`, a path or the answer's `url`); only site paths and http(s) URLs, escaped, as a plain `<a href>`. `applyOutbound` gives an http(s) link to another host the editor's `rel` plus `nofollow` or `sponsored` from the answer's `outbound` domain lists (subdomains included) and, only while `newTab` (`outbound.new_tab`) is on, `target="_blank"`, `rel="noopener"` and, when `hint` is set (`useSeoLabels().value.links.newTab` while `seo.links.newTabHint` is on), a trailing `<span class="seo-visually-hidden">`; internal, relative and non-http links stay as they are. `imageAttrs` adds `loading="lazy"` and `decoding="async"` to body images and keeps their stored width, height and `alt` (`alt=""` included). They expect sanitize-html output (normalised tags, escaped text) and only add escaped markup between tags.
|
||||
- `mergeGraph(cmsJsonld, nodes)`, `isPagePath(path, resolveFiles)`, `pickLabels(locale, htmlLang, overrides)`, `formatSeoLabel(template, values)`, `seoEnabled(publicSeo)`: pure helpers (v0.2.0).
|
||||
- `seoHeadInput`, `seoAbsolute`, `seoRedirectTarget`, `seoRobots`, `seoXRobotsTag`, `seoResolvableUrl`, `seoAnswer`, `seoVerificationName`: the pure helpers behind them (`app/utils/seo.ts`), auto-imported.
|
||||
|
||||
@@ -121,7 +122,7 @@ export default defineAppConfig({
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
pnpm test # node --test on the helpers, the cache, the purge and the labels
|
||||
pnpm test # node --test on the helpers, the body link rules, the cache, the purge and the labels
|
||||
pnpm build:playground
|
||||
pnpm smoke:ssr # the playground against testing/fixture-api.mjs, seven runs
|
||||
pnpm purge:check # testing/purge-check.mjs and purge-token-check.mjs
|
||||
|
||||
Reference in New Issue
Block a user