スキル一覧に戻る
webdevcody

agentic-jumpstart-performance

by webdevcody

15🍴 10📅 2026年1月22日
GitHubで見るManusで実行

SKILL.md


name: agentic-jumpstart-performance description: Performance optimization patterns for TanStack Start with React 19, Vite, Drizzle ORM, and TanStack Query. Use when optimizing page load times, database queries, bundle size, caching, memoization, lazy loading, or when the user mentions performance, speed, optimization, or slow.

Performance Optimization

React 19 Performance Patterns

Code Splitting with Lazy Loading

import { lazy, Suspense } from "react";

// Lazy load heavy components
const HeavyEditor = lazy(() => import("./HeavyEditor"));
const DataVisualization = lazy(() => import("./DataVisualization"));

function Dashboard() {
  return (
    <Suspense fallback={<LoadingSkeleton />}>
      <HeavyEditor />
    </Suspense>
  );
}

Strategic Memoization

import { memo, useMemo, useCallback, useDeferredValue } from "react";

// Memoize expensive computations
const expensiveResult = useMemo(() => {
  return processLargeDataset(data);
}, [data]);

// Memoize callbacks passed to children
const handleClick = useCallback((id: number) => {
  updateItem(id);
}, [updateItem]);

// Defer non-critical updates
const deferredSearchTerm = useDeferredValue(searchTerm);

// Memoize components that receive stable props
const MemoizedList = memo(function List({ items }: { items: Item[] }) {
  return items.map(item => <ListItem key={item.id} item={item} />);
});

Avoid Re-renders

// BAD: Creates new object every render
<Component style={{ color: "red" }} />

// GOOD: Define outside or memoize
const style = { color: "red" };
<Component style={style} />

// BAD: Creates new function every render
<Button onClick={() => handleClick(id)} />

// GOOD: Use useCallback or define handler
const handleButtonClick = useCallback(() => handleClick(id), [id]);
<Button onClick={handleButtonClick} />

TanStack Query Optimization

Caching Configuration

import { queryOptions } from "@tanstack/react-query";

// Configure stale time based on data freshness needs
export const segmentsQueryOptions = () =>
  queryOptions({
    queryKey: ["segments"],
    queryFn: () => getSegmentsFn(),
    staleTime: 5 * 60 * 1000, // 5 minutes
    gcTime: 30 * 60 * 1000,   // 30 minutes cache
  });

// For frequently changing data
export const statsQueryOptions = () =>
  queryOptions({
    queryKey: ["stats"],
    queryFn: () => getStatsFn(),
    staleTime: 30 * 1000,     // 30 seconds
    refetchInterval: 60 * 1000, // Auto-refetch every minute
  });

Prefetching

// Prefetch on route load
export const Route = createFileRoute("/courses/$courseId")({
  loader: async ({ params, context }) => {
    await context.queryClient.ensureQueryData(
      courseQueryOptions(params.courseId)
    );
  },
  component: CoursePage,
});

// Prefetch on hover
function CourseCard({ course }) {
  const queryClient = useQueryClient();

  const handleMouseEnter = () => {
    queryClient.prefetchQuery(courseQueryOptions(course.id));
  };

  return (
    <Link to={`/courses/${course.id}`} onMouseEnter={handleMouseEnter}>
      {course.title}
    </Link>
  );
}

Optimistic Updates

const mutation = useMutation({
  mutationFn: updateSegmentFn,
  onMutate: async (newData) => {
    await queryClient.cancelQueries({ queryKey: ["segments"] });
    const previous = queryClient.getQueryData(["segments"]);

    queryClient.setQueryData(["segments"], (old) =>
      old.map((s) => (s.id === newData.id ? { ...s, ...newData } : s))
    );

    return { previous };
  },
  onError: (err, newData, context) => {
    queryClient.setQueryData(["segments"], context.previous);
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ["segments"] });
  },
});

Database Query Optimization

Select Only Required Columns

// BAD: Fetches all columns
const users = await database.select().from(users);

// GOOD: Select only what you need
const users = await database
  .select({ id: users.id, name: users.name, email: users.email })
  .from(users);

Use Indexes Effectively

// Ensure indexes exist for frequently queried columns
// In schema definition:
export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  email: varchar("email", { length: 255 }).notNull().unique(),
  createdAt: timestamp("created_at").defaultNow(),
}, (table) => ({
  emailIdx: index("email_idx").on(table.email),
  createdAtIdx: index("created_at_idx").on(table.createdAt),
}));

Avoid N+1 Queries

// BAD: N+1 query pattern
const segments = await getSegments();
for (const segment of segments) {
  segment.attachments = await getAttachments(segment.id);
}

// GOOD: Single query with join
const segmentsWithAttachments = await database
  .select()
  .from(segments)
  .leftJoin(attachments, eq(segments.id, attachments.segmentId));

// Or use Drizzle's with clause
const result = await database.query.segments.findMany({
  with: {
    attachments: true,
    module: true,
  },
});

Pagination

export async function getPaginatedUsers(page: number, limit: number) {
  const offset = (page - 1) * limit;

  const [data, [{ count }]] = await Promise.all([
    database
      .select()
      .from(users)
      .orderBy(desc(users.createdAt))
      .limit(limit)
      .offset(offset),
    database.select({ count: sql`count(*)` }).from(users),
  ]);

  return {
    data,
    pagination: {
      page,
      limit,
      total: Number(count),
      totalPages: Math.ceil(Number(count) / limit),
    },
  };
}

Bundle Optimization

Vite Configuration

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Split vendor chunks
          "react-vendor": ["react", "react-dom"],
          "query-vendor": ["@tanstack/react-query"],
          "ui-vendor": ["@radix-ui/react-dialog", "@radix-ui/react-dropdown-menu"],
        },
      },
    },
    // Enable minification
    minify: "terser",
    terserOptions: {
      compress: {
        drop_console: true, // Remove console.logs in production
      },
    },
  },
});

Dynamic Imports for Large Libraries

// Dynamically import heavy libraries
async function processMarkdown(content: string) {
  const { marked } = await import("marked");
  return marked(content);
}

// For React components
const RichTextEditor = lazy(() => import("./RichTextEditor"));
const ChartComponent = lazy(() => import("recharts").then(mod => ({
  default: mod.ResponsiveContainer
})));

Image & Media Optimization

Lazy Loading Images

function OptimizedImage({ src, alt }: { src: string; alt: string }) {
  return (
    <img
      src={src}
      alt={alt}
      loading="lazy"
      decoding="async"
    />
  );
}

Video Loading Strategy

function VideoPlayer({ videoKey }: { videoKey: string }) {
  const [videoUrl, setVideoUrl] = useState<string | null>(null);

  // Load video URL only when component is visible
  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        loadVideoUrl(videoKey).then(setVideoUrl);
        observer.disconnect();
      }
    });

    observer.observe(containerRef.current);
    return () => observer.disconnect();
  }, [videoKey]);

  return videoUrl ? <video src={videoUrl} controls /> : <VideoSkeleton />;
}

Tailwind CSS Optimization

Purge Unused CSS

Tailwind v4 automatically purges unused styles in production. Ensure your content paths are correct:

// tailwind.config.ts
export default {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./src/components/**/*.{js,ts,jsx,tsx}",
  ],
};

Use CSS Variables for Theming

/* Avoid redundant utility classes */
:root {
  --primary: theme(colors.blue.600);
  --primary-hover: theme(colors.blue.700);
}

Memory Leak Prevention

Cleanup Effects

useEffect(() => {
  const controller = new AbortController();

  fetchData({ signal: controller.signal })
    .then(setData)
    .catch((err) => {
      if (err.name !== "AbortError") {
        setError(err);
      }
    });

  return () => controller.abort();
}, []);

Event Listener Cleanup

useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth);
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);

Performance Checklist

  • Heavy components are lazy loaded with Suspense
  • TanStack Query has appropriate staleTime/gcTime
  • Database queries select only needed columns
  • Joins are used instead of N+1 queries
  • Large lists are paginated
  • Bundle chunks are optimized for caching
  • Images use loading="lazy"
  • Effects clean up subscriptions and abort controllers
  • Memoization is used strategically (not everywhere)
  • Prefetching is used for predictable navigation

スコア

総合スコア

65/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

+5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です