Search

PythonReactSEOPDF
GitHub Skill

frontend-development

Frontend development patterns for the Flare Stack Blog. Use when implementing data fetching with TanStack Query, creating route loaders, building infinite scroll, adding skeleton/pending states, or organizing UI components.

du2333☆ 1 Repository stars0 Repository forks

Installation

Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.

View source ↗Guides

Skill instructions

Frontend Development Patterns

This skill covers TanStack Query patterns, route loaders, component organization, and frontend best practices.

Data Fetching Architecture

The project follows TanStack Start / TanStack Query standard practices for seamless SSR and client-side caching.

1. Query Definition

Organize query definitions in features/<name>/queries/index.ts. Use a Query Key Factory pattern to centralize and type-safe your cache keys.

// features/posts/queries/index.ts
import { queryOptions, infiniteQueryOptions } from "@tanstack/react-query";
import { findPostBySlugFn, getPostsFn } from "../api/posts.api";

// 1. Define Query Key Factory (static arrays + functions)
export const POSTS_KEYS = {
  all: ["posts"] as const,

  // Parent keys (static arrays for prefix invalidation)
  lists: ["posts", "list"] as const,
  details: ["posts", "detail"] as const,
  adminLists: ["posts", "admin-list"] as const,

  // Child keys (functions for specific queries)
  list: (filters?: { tagName?: string }) =>
    ["posts", "list", filters] as const,
  detail: (idOrSlug: number | string) =>
    ["posts", "detail", idOrSlug] as const,
  adminList: (params: GetPostsInput) =>
    ["posts", "admin-list", params] as const,
};

// 2. Define Query Options
export function postBySlugQuery(slug: string) {
  return queryOptions({
    queryKey: POSTS_KEYS.detail(slug),
    queryFn: () => findPostBySlugFn({ data: { slug } }),
  });
}

export function postsInfiniteQuery(tag?: string) {
  return infiniteQueryOptions({
    queryKey: POSTS_KEYS.list({ tagName: tag }),
    queryFn: ({ pageParam }) =>
      getPostsFn({ data: { cursor: pageParam, tag } }),
    getNextPageParam: (lastPage) => lastPage.nextCursor,
    initialPageParam: undefined,
  });
}

Key Pattern:

  • Parent keys are static arrays (no ()) — used for prefix-based cache invalidation
  • Child keys are functions — used for specific queries with parameters
// Invalidation (no parentheses - static reference)
queryClient.invalidateQueries({ queryKey: POSTS_KEYS.adminLists });

// Query (with parentheses - function call)
useQuery({ queryKey: POSTS_KEYS.detail(postId) });
2. Route Loader (routes/<path>.tsx)

Use loader functions with ensureQueryData() or prefetchQuery() for SSR:

// $slug.tsx
export const Route = createFileRoute("/_public/post/$slug")({
  loader: async ({ context, params }) => {
    const post = await context.queryClient.ensureQueryData(
      postBySlugQuery(params.slug),
    );
    if (!post) throw notFound();
    return post;
  },
  head: ({ loaderData: post }) => ({
    meta: [{ title: post?.title }],
  }),
  component: PostPage,
});

Key Points:

  • ensureQueryData(): Fetches and caches if not present
  • prefetchQuery() / prefetchInfiniteQuery(): For background prefetching
  • loaderData: Available in head function for dynamic SEO
3. Component Data Access

Use useSuspenseQuery or useSuspenseInfiniteQuery in components. Since loaders prefetch, this is typically synchronous:

function PostComponent() {
  const { slug } = Route.useParams();
  const { data: post } = useSuspenseQuery(postBySlugQuery(slug));

  return <article>{post.content}</article>;
}
4. Infinite Scroll Pattern

Use IntersectionObserver to observe a bottom sentinel element:

function PostList() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
    useSuspenseInfiniteQuery(postsInfiniteQuery());

  const sentinelRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
          fetchNextPage();
        }
      },
      { threshold: 0.1 }
    );

    if (sentinelRef.current) observer.observe(sentinelRef.current);
    return () => observer.disconnect();
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  return (
    <>
      {data.pages.flatMap((page) => page.posts.map((post) => (
        <PostCard key={post.id} post={post} />
      )))}

      <div ref={sentinelRef} className="h-4" />
      {isFetchingNextPage && <LoadingSpinner />}
    </>
  );
}

Skeleton / Pending States

Define pendingComponent for critical routes to show skeletons during client navigation:

export const Route = createFileRoute("/_public/post/$slug")({
  pendingComponent: PostSkeleton,
  // ...
});

function PostSkeleton() {
  return (
    <div className="animate-pulse">
      <div className="h-8 bg-muted rounded w-3/4 mb-4" />
      <div className="h-4 bg-muted rounded w-full mb-2" />
      <div className="h-4 bg-muted rounded w-5/6" />
    </div>
  );
}

Component Organization

src/components/ Directory
Subdirectory Purpose
ui/ Atomic UI components (Button, Input, Card). Use cva for variants.
common/ Shared business components (ThemeProvider, LoadingFallback)
layout/ Layout components (Header, Footer, Sidebar)
Feature Components

Components specific to a feature belong in features/<name>/components/.

Custom Hooks (src/hooks/)

Reusable hooks for cross-feature functionality:

Hook Purpose
use-debounce.ts Generic debouncing
use-media-query.ts Responsive media queries
use-navigate-back.ts Navigation with fallback
use-active-toc.ts Table of contents active state

Naming Conventions

Type Convention Example
Components & Hooks kebab-case files post-item.tsx, use-debounce.ts
React Components PascalCase PostItem
Variables & Functions camelCase getPosts
Types & Interfaces PascalCase PostItemProps

Examples

Read the usage examples in the source repository.

View source ↗

Source history

Indexed on
2026-01-20
Index updated
2026-01-26
Branch
main

Index and sync dates describe this directory. View the source repository for code history.

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!