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



<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 featured articles in a slider, the categories in a sidebar and the latest articles in a grid. It leads to the other four blog pages: [article](https://docs.salla.dev/twilight/react/routes/blog-article), [category](https://docs.salla.dev/twilight/react/routes/blog-category), [tag](https://docs.salla.dev/twilight/react/routes/blog-tag) and [author](https://docs.salla.dev/twilight/react/routes/blog-author). Customise this route to change the layout of the blog home or its title.

## URL [#url]

| URL              | Example    |
| ---------------- | ---------- |
| `/blog`          | `/blog`    |
| `/{locale}/blog` | `/ar/blog` |

The only param is the optional `locale` ([locale prefix](https://docs.salla.dev/twilight/react/routes/home#locale-prefix)). There are no search params.

### Try it [#try-it]

This is the blog as the engine renders it, running against the demo store. Switch the language or the width.

<RoutePreview path="/blog" pattern="/{locale}/blog" />

## Route file [#route-file]

The engine writes this file into `app/routes/blog.tsx`.

```tsx title="app/routes/blog.tsx"
// @auto-generated
import { createFileRoute } from '@tanstack/react-router';
import { Blog } from '@salla.sa/twilight-theme-engine/routes/blog';
import type { BlogPageProps } 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';

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

function BlogComponent() {
  const data: BlogPageProps = Route.useLoaderData();
  return <Blog.Component {...data} />;
}
```

To take the page over, delete the `// @auto-generated` line. Keep the `createFileRoute` path as it is. `Blog` gives you the `loader`, `head` and stock `Component`, so you can change any one of them.

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

This keeps the engine's loader and skeleton, adds a suffix to the title and draws a plain list. The stock page's [hook slots](https://docs.salla.dev/twilight/react/guide/hooks-and-slots) are kept so merchant apps still work.

```tsx title="app/routes/blog.tsx"
import { createFileRoute } from '@tanstack/react-router';
import { Blog } from '@salla.sa/twilight-theme-engine/routes/blog';
import type { BlogPageProps } 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')({
  loader: ({ params }): Promise<BlogPageProps> => Blog.loader({ locale: params.locale }),
  head: withHead(Blog, (result) => ({ ...result, title: `${result.title} | Journal` })),
  pendingComponent: () => <BlogSkeleton />,
  component: JournalPage,
});

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

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

`Blog.loader` returns `BlogPageProps`:

| Field        | Type               | What it is                                                     |
| ------------ | ------------------ | -------------------------------------------------------------- |
| `page`       | `Page`             | `title` (the translated "Blog") and `breadcrumbs` (Home, Blog) |
| `articles`   | `ArticleSummary[]` | The latest articles                                            |
| `categories` | `BlogCategory[]`   | Sidebar categories                                             |
| `slides`     | `ArticleSummary[]` | Featured articles for the slider                               |
| `popular`    | `ArticleSummary[]` | Popular articles                                               |

All four lists come from one request in the [blog API module](https://docs.salla.dev/twilight/react/api/modules/blog). The first request is rendered on the server, so the HTML already has the articles. The loader accepts an optional second argument, `extend(data, ctx)`, to merge your own fields into the data.

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

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

`Blog.head` sets the title and adds hreflang links for every store language. No description is set; add one with `withHead(Blog, (result) => ({ ...result, description: '...' }))`. See [SEO and head](https://docs.salla.dev/twilight/react/guide/seo-and-head).

## Edge cases [#edge-cases]

* **The blog API fails.** The loader returns empty lists, so the page shows its "no articles" state instead of an error page. Handle `articles.length === 0` in a replacement component.
* **No featured slides.** The slider is not rendered and a plain heading is shown. With neither slides nor articles, the page shows the empty state.
* **One batch only.** Loading more articles happens on the [category](https://docs.salla.dev/twilight/react/routes/blog-category), [tag](https://docs.salla.dev/twilight/react/routes/blog-tag) and [author](https://docs.salla.dev/twilight/react/routes/blog-author) pages.
* **Locale.** The `locale` you pass to the loader is not used; the content language follows the request, so `/ar/blog` returns Arabic content.

## See also [#see-also]

* [Blog article](https://docs.salla.dev/twilight/react/routes/blog-article), [Blog category](https://docs.salla.dev/twilight/react/routes/blog-category)
* [Fork a built-in route](https://docs.salla.dev/twilight/react/recipes/fork-a-built-in-route)
* [Routes overview](https://docs.salla.dev/twilight/react/routes)
