Skip to content
Twilight React Playground
ثيم رائدaren

Blog

route-moduleBeginnerserverbrowserlive demo

The blog home page with featured slides, categories and latest articles, rendering empty lists rather than an error when the blog fails to load.

import { Blog, BlogPage, blogLoader, BlogPageProps } from '@salla.sa/twilight-theme-engine/routes/blog';

In plain words

Salla stores can publish a blog. Its home page at /blog shows featured articles in a slider, the categories beside them and a list of articles. Blog.loader fetches all of it in one request.

The component only draws the data it is given, so a route can change what appears (drop the slider, show fewer articles) through the loader's extend argument, as the demo does. The real page runs on Built-in pages.

Signature

const Blog: {
  readonly id: 'blog.index';
  readonly loader: (
    ctx?: { locale?: string },
    extend?: (data: BlogPageProps, ctx: { params: {} }) => Record<string, unknown> | Promise<Record<string, unknown>>
  ) => Promise<BlogPageProps>;
  readonly head: (ctx: TwilightContext, data: BlogPageProps) => HeadDescriptor;
  readonly Component: React.LazyExoticComponent<(props: BlogPageProps) => JSX.Element>;
};

const BlogPage: typeof Blog.Component;
function blogLoader(): Promise<BlogPageProps>;

interface BlogPageProps {
  page: Page;                        // title: the blocks.footer.blog translation, or 'Blog'
  articles: ArticleSummary[];
  categories?: BlogCategoryType[];
  slides?: ArticleSummary[];
  popular?: ArticleSummary[];        // loaded, not drawn by BlogPage
}

Try it live

The engine's blog home drawn from this store's real blog, with the changes a route's extend argument can make to its data.Try this: turn the slider off: with no slides the page shows its own heading instead. Then set the article count to 0.
Storefront canvas · en · LTR

Loading the blog…

Controls
Keep the slides
What a theme writes
// app/routes.ts: route('/blog', 'blog-custom.tsx')
import { createFileRoute } from '@tanstack/react-router';
import { Blog, 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';

export const Route = createFileRoute('/{-$locale}/blog')({
  loader: ({ params }): Promise<BlogPageProps> =>
    Blog.loader({ locale: params.locale }, (data) => ({ articles: data.articles.slice(0, 4) })),
  head: withHead(Blog),
  pendingComponent: () => <BlogSkeleton />,
  component: BlogCustom,
});

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

Example

app/routes/blog.tsx (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';

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} />;
}

How it behaves

  • URL: /{-$locale}/blog, with BlogSkeleton as the pending component.

  • blogLoader reads the blog overview and builds the breadcrumbs Home › Blog. It takes no locale: the API language comes from the request.

  • head sets the title and hreflang alternates, plus a description only when page.description is set, which the loader never does.

  • BlogPage renders the slides in Salla's slider web component, the categories as a sidebar on large screens, the article cards, and the slots blog:start, blog:items.start, blog:items.end and blog:end. With no slides it shows its own heading; with no slides and no articles, NoContent.

  • There is no public blog API subpath. Outside a loader, read the blog with the API client: api.get('blog').json() from @salla.sa/twilight-theme-engine/api/client, whose lists can be null (the demo shows how).

  • Lazy: outside a route, wrap BlogPage in <Suspense>.

Gotchas

  • blogLoader catches every error and returns the page with empty articles, categories, slides and popular, without logging. A store without a blog and a failed request look the same: an empty blog. Check the network panel before assuming the blog is empty.

Related

Source and docs