# Blog tag (https://docs.salla.dev/twilight/react/routes/blog-tag)



<Callout title="Framework: TanStack Start">
  This page is written for TanStack Start, the default and currently supported framework. [See what depends on the framework](https://docs.salla.dev/twilight/react/start/frameworks).
</Callout>

The customer sees the articles that carry one tag, under the tag's name, and more load as they scroll. Tag links appear on articles and article cards. Customise this route to change the list layout or the title. This is the blog tag; products have their [own tag page](https://docs.salla.dev/twilight/react/routes/tag).

## URL [#url]

| URL                              | Example                 |
| -------------------------------- | ----------------------- |
| `/blog/$slug/tag-{$id}`          | `/blog/launch/tag-7`    |
| `/{locale}/blog/$slug/tag-{$id}` | `/ar/blog/launch/tag-7` |

### Params [#params]

| Name     | Type                  | Example  | Description                                             |
| -------- | --------------------- | -------- | ------------------------------------------------------- |
| `slug`   | `string`              | `launch` | For readable URLs. The loader never reads it.           |
| `id`     | `string`              | `7`      | The tag id. The `tag-` is part of the URL.              |
| `locale` | `string \| undefined` | `ar`     | Optional ([locale prefix](https://docs.salla.dev/twilight/react/routes/home#locale-prefix)). |

Product tags live at [`/$slug/tag-{$id}`](https://docs.salla.dev/twilight/react/routes/tag), without the `/blog` prefix.

### Search params [#search-params]

None. Paging is not in the URL.

## Route file [#route-file]

The engine writes this into `app/routes/blog_.$slug.tag-$id.tsx`.

```tsx title="app/routes/blog_.$slug.tag-$id.tsx"
// @auto-generated
import { createFileRoute } from '@tanstack/react-router';
import { BlogTagRoute } from '@salla.sa/twilight-theme-engine/routes/blog';
import type { BlogTagLoaderData } from '@salla.sa/twilight-theme-engine/routes/blog';
import { BlogSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';

/**
 * BlogTagRoute route configuration.
 * Loads page data via loader and renders the BlogTagRoute component.
 */
export const Route = createFileRoute('/{-$locale}/blog/$slug/tag-{$id}')({
  loader: ({ params }): Promise<BlogTagLoaderData> =>
    BlogTagRoute.loader({ params: { slug: params.slug, id: params.id }, locale: params.locale }),
  head: withHead(BlogTagRoute),
  pendingComponent: () => <BlogSkeleton />,
  component: BlogTagRouteComponent,
});

function BlogTagRouteComponent() {
  const data: BlogTagLoaderData = Route.useLoaderData();
  return <BlogTagRoute.Component {...data} />;
}
```

Delete the `// @auto-generated` line to own the file. Keep the path in `createFileRoute`.

### A customised version [#a-customised-version]

This keeps the engine's loader, head and skeleton and draws a plain list of the first batch.

```tsx title="app/routes/blog_.$slug.tag-$id.tsx"
import { createFileRoute } from '@tanstack/react-router';
import { BlogTagRoute } from '@salla.sa/twilight-theme-engine/routes/blog';
import type { BlogTagLoaderData } from '@salla.sa/twilight-theme-engine/routes/blog';
import { BlogSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';
import { HookSlot } from '@salla.sa/twilight-theme-engine/hooks';
import { Breadcrumb, Link, NoContent } from '@salla.sa/twilight-theme-engine/components/common';

export const Route = createFileRoute('/{-$locale}/blog/$slug/tag-{$id}')({
  loader: ({ params }): Promise<BlogTagLoaderData> =>
    BlogTagRoute.loader({ params: { slug: params.slug, id: params.id }, locale: params.locale }),
  head: withHead(BlogTagRoute),
  pendingComponent: () => <BlogSkeleton />,
  component: TagPage,
});

function TagPage() {
  const { page, blogTag, articles }: BlogTagLoaderData = Route.useLoaderData();
  return (
    <div className="container">
      <Breadcrumb page={page} />
      <HookSlot name="blog:tag.start" />
      <h1>#{blogTag.name}</h1>
      {articles.length === 0 ? (
        <NoContent icon="sicon-inbox" message="No articles with this tag" />
      ) : (
        <ul>
          {articles.map((a) => (
            <li key={a.id}>
              <Link to={a.url}>{a.name}</Link>
            </li>
          ))}
        </ul>
      )}
      <HookSlot name="blog:tag.end" />
    </div>
  );
}
```

## What it loads [#what-it-loads]

`BlogTagRoute.loader` returns `BlogTagLoaderData`:

| Field        | Type               | What it is                                               |
| ------------ | ------------------ | -------------------------------------------------------- |
| `page`       | `Page`             | `title` and `id` of the tag; breadcrumbs Home, Blog, tag |
| `blogTag`    | `BlogTag`          | `id` (a number), `name`, `url`                           |
| `categories` | `BlogCategory[]`   | All categories, for the sidebar                          |
| `articles`   | `ArticleSummary[]` | The first batch of articles                              |
| `cursor`     | `Pagination`       | `next`, `previous`, `current`                            |

It makes two requests at once: this tag's articles and the blog overview (for the sidebar), through the [blog API module](https://docs.salla.dev/twilight/react/api/modules/blog). The first request is rendered on the server. Further batches are fetched only in the browser.

[Hook slots](https://docs.salla.dev/twilight/react/guide/hooks-and-slots): `blog:tag.start`, `blog:tag.end`.

## Head and SEO [#head-and-seo]

`BlogTagRoute.head` uses `blogTag.name` as the title, the English text `Articles tagged with {name}` as the description, `blogTag.url` as the canonical link, and hreflang links. The description is not translated; override it with `withHead(BlogTagRoute, (result) => ({ ...result, description: '...' }))`. See [SEO and head](https://docs.salla.dev/twilight/react/guide/seo-and-head).

## Edge cases [#edge-cases]

* **More articles than one batch.** When `cursor.next` is set, the stock page loads the next batches as the customer scrolls, in the browser only. A custom page that shows only `articles` shows only the first batch.
* **Empty tag.** The page shows the "no articles" state and no heading.
* **Unknown tag id.** The loader throws a not-found error, so the customer sees the [not-found page](https://docs.salla.dev/twilight/react/routes/not-found) instead of a server error.
* **Wrong `$slug`.** Ignored, no redirect.
* **Not the product tag.** `/blog/x/tag-7` is a blog tag; `/x/tag-7` and `/tags/7` are product tags.
* **Locale.** The `locale` you pass to the loader is not used; the language follows the request.

## See also [#see-also]

* [Blog](https://docs.salla.dev/twilight/react/routes/blog), [Blog article](https://docs.salla.dev/twilight/react/routes/blog-article), [Blog category](https://docs.salla.dev/twilight/react/routes/blog-category)
* [Product tag](https://docs.salla.dev/twilight/react/routes/tag)
* [Routes overview](https://docs.salla.dev/twilight/react/routes)
