#Canonical Woes - Starlight

1 messages · Page 1 of 1 (latest)

wintry fable
#

I want to override my canonical tag, but for some reason Starlight keeps fighting me
what i'm trying to achieve: to have canonical tag without traling slash + without the .html extension

(i understand astro in general don't like non trailing slash without .html, but i'm still able to do this since my hosting handles it, it will automatically strip the html extension, so the url becomes pretty, but i also need my canonical to be a clean url)

my problem with astro is, i tried to override canonical via overriding the head tags

  • tried to override component (doesn't work)
  • tried to override via frontmatter (doesn't work)

what i notice with starlight is, once i set my site in astro.config.mjs it just automatically sets the canonical (along with og:url) and i can't override this

(code in comment)

#

with site value set in config, it automatically sets canonical

export default defineConfig({
  site: "https://corsfix.com",
  base: "/docs",
  trailingSlash: "never",
  build: {
    format: "file",
  },
...
#

damn discord put my image in the main post lol, but yeah it auto sets the canonical

#

this fronmatter setting doesn't help (random value for sanity check)

---
title: API
description: Corsfix CORS Proxy API documentation.
head:
  - tag: meta
    attributes:
      rel: canonical
      href: https://corsfix.com/asdfasdf
---
#

I also override the Head.astro component as follows (this used to work)
(yes i already have override in my astro config)

---
import type { Props } from "@astrojs/starlight/props";
import Default from "@astrojs/starlight/components/Head.astro";

const canonical = Astro.site
  ? new URL(Astro.url.pathname.replace(/\.html$/, ""), Astro.site)
  : undefined;

console.log("Canonical URL:", canonical?.href);

const modifiedData = {
  ...Astro.locals.starlightRoute.entry.data,
  head: [
    ...Astro.locals.starlightRoute.entry.data.head,
    {
      tag: "link",
      attrs: {
        rel: "canonical",
        href: canonical?.href,
      },
    },
    {
      tag: "meta",
      attrs: {
        property: "og:url",
        content: canonical?.href,
      },
    },
    {
      tag: "meta",
      attrs: {
        property: "twitter:url",
        content: canonical?.href,
      },
    },
  ],
};

const modifiedEntry = {
  ...Astro.locals.starlightRoute.entry,
  data: modifiedData,
};
---

<Default
  {...Astro.locals.starlightRoute}
  entry={modifiedEntry as Props["entry"]}><slot /></Default
>
#

console log output

14:04:22 [vite] Re-optimizing dependencies because vite config has changed
Canonical URL: https://corsfix.com/docs/cors-proxy/api
14:04:26 [200] /cors-proxy/api 94ms
Canonical URL: https://corsfix.com/docs/cors-proxy/api
14:04:27 [200] /cors-proxy/api 14ms
rich bluff
#

Oh interesting case, thanks for sharing!

I guess with that override you end up with two canonical tags? One "wrong" one generated by Starlight and then lower down yours?

wintry fable
#

here is my live website, with starlight 0.31, canonical override working
https://corsfix.com/docs/cors-proxy/api

it only have 1 canonical, i believe astro is smart enough to override and not have duplicate

Corsfix Docs
API

Corsfix CORS Proxy API documentation.

wintry fable
rich bluff
#

I think the way I'd do this with newer versions of Starlight is to use a route middleware instead of the override.

In route middleware, I'd search inside the head data to find the automatically generated canonical and format it as desired.

wintry fable
wintry fable
rich bluff
#

Middleware I'd try:

import { defineRouteMiddleware } from '@astrojs/starlight/route-data';

export const onRequest = defineRouteMiddleware((context) => {
  const canonicalLink = context.locals.starlightRoute.head.find(tag => tag.rel === 'canonical');
  if (canonical) {
    canonical.href = formatUrl(canonical.href);
  }
});
#

On my phone otherwise I'd spin up a little example to debug.

wintry fable
#

@rich bluff that seems to be the fix, obviously i have to adjust the code to make it work
i'm surprised the middleware happens on build time, i could've sworn i remember something that happens during runtime, like changing stuff via JS

#

thanks for your help!

rich bluff
#

No problem! Yeah we call it "middleware" but basically it's just a way to modify Starlight's data for each page at build time. So that sits in between the Markdown frontmatter etc. and the page templates. The intention was to make it a bit easier to do manipulations like this without needing component overrides (especially useful for data used by multiple components).

wintry fable
wintry fable
#

credits to Chris for helping of course 🙏

rich bluff
#

Glad you got it working!

#

And sorry it wasn’t a smoother experience — looking at that file, I wish we had a cleaner way to specify expected link formats like you want.

wintry fable
#

hmm, i'm not sure if i'm the right person to make this statement, since i don't know the inner workings of starlight/astro

from my research, astro in general only really support 2 types of url

  • trailing slash: www.web.site/path/index.html
  • non trailing slash: www.web.site/path.html

(cloudflare pages kinda cleans this up a bit, by removing the html, so it either becomes a /path/ or a /path)

i guess my use case of having a clean non trailing slash url is not the inteded way for the framework (?)
that's why i kinda expected it will be hacky

rich bluff
#

Astro supports those two output types, yeah. But in Starlight we kind of abuse the “file output” settings to decide how to format URLs, which those settings aren’t strictly speaking intended for.

wintry fable
#

i see, i think i get what you mean, so instead of the project directory/path, it's using the file output setting, which is why it all has the .html extensions

probably need some assessment if starlight team wants to consider this (use case), since it is pretty niche thing

atleast i think i'm the only one pushing it this far XD