diff --git a/.kiro/specs/ultimate-lists-app/tasks.md b/.kiro/specs/ultimate-lists-app/tasks.md
index 153e227..ed3e225 100644
--- a/.kiro/specs/ultimate-lists-app/tasks.md
+++ b/.kiro/specs/ultimate-lists-app/tasks.md
@@ -215,7 +215,7 @@
- Sanitize user inputs
- _Requirements: 1.1, 2.1_
-- [ ] 18. Optimize performance
+- [-] 18. Optimize performance
- Add database indexes for frequently queried fields
- Implement pagination for large lists
- Optimize bundle size with code splitting
diff --git a/components/EmptyState.tsx b/components/EmptyState.tsx
index f46c68d..b92b696 100644
--- a/components/EmptyState.tsx
+++ b/components/EmptyState.tsx
@@ -1,6 +1,6 @@
'use client'
-import { ReactNode } from 'react'
+import { memo, ReactNode } from 'react'
import { Inbox, ListTodo, Search, Filter } from 'lucide-react'
interface EmptyStateProps {
@@ -10,7 +10,7 @@ interface EmptyStateProps {
action?: ReactNode
}
-export default function EmptyState({ icon = 'inbox', title, description, action }: EmptyStateProps) {
+function EmptyState({ icon = 'inbox', title, description, action }: EmptyStateProps) {
const icons = {
inbox: ,
list: ,
@@ -39,3 +39,5 @@ export default function EmptyState({ icon = 'inbox', title, description, action
)
}
+
+export default memo(EmptyState)
diff --git a/components/FilterPanel.tsx b/components/FilterPanel.tsx
index 81554f8..73b3488 100644
--- a/components/FilterPanel.tsx
+++ b/components/FilterPanel.tsx
@@ -1,6 +1,6 @@
'use client'
-import { useState } from 'react'
+import { memo, useState } from 'react'
import { Filter, X, ChevronDown, ChevronUp } from 'lucide-react'
export interface FilterState {
@@ -19,7 +19,7 @@ interface FilterPanelProps {
showListScope?: boolean
}
-export default function FilterPanel({
+function FilterPanel({
filters,
onFiltersChange,
availableTags = [],
@@ -294,3 +294,5 @@ export default function FilterPanel({
)
}
+
+export default memo(FilterPanel)
diff --git a/components/ItemCard.tsx b/components/ItemCard.tsx
index d4de6be..9b1a7fd 100644
--- a/components/ItemCard.tsx
+++ b/components/ItemCard.tsx
@@ -1,5 +1,6 @@
'use client'
+import { memo } from 'react'
import Link from 'next/link'
import { Calendar, Tag, AlertCircle } from 'lucide-react'
import ToggleItemButton from './ToggleItemButton'
@@ -19,7 +20,7 @@ interface ItemCardProps {
listId: string
}
-export default function ItemCard({ item, listId }: ItemCardProps) {
+function ItemCard({ item, listId }: ItemCardProps) {
const tags = Array.isArray(item.tags) ? item.tags : []
const isOverdue = item.dueDate && new Date(item.dueDate) < new Date() && !item.completed
@@ -104,3 +105,5 @@ export default function ItemCard({ item, listId }: ItemCardProps) {
)
}
+
+export default memo(ItemCard)
diff --git a/components/ListCard.tsx b/components/ListCard.tsx
index 435925d..b3cb0dc 100644
--- a/components/ListCard.tsx
+++ b/components/ListCard.tsx
@@ -1,7 +1,7 @@
'use client'
+import { memo, useState } from 'react'
import Link from 'next/link'
-import { useState } from 'react'
import { Edit2 } from 'lucide-react'
import Modal from './Modal'
import ListForm from './ListForm'
@@ -17,7 +17,7 @@ interface List {
}>
}
-export default function ListCard({ list }: { list: List }) {
+function ListCard({ list }: { list: List }) {
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
const completedCount = list.items.filter(item => item.completed).length
const totalCount = list.items.length
@@ -94,4 +94,6 @@ export default function ListCard({ list }: { list: List }) {
>
)
-}
\ No newline at end of file
+}
+
+export default memo(ListCard)
\ No newline at end of file
diff --git a/components/ListDetailView.tsx b/components/ListDetailView.tsx
index d2877a8..1ab2eb3 100644
--- a/components/ListDetailView.tsx
+++ b/components/ListDetailView.tsx
@@ -93,7 +93,9 @@ export default function ListDetailView({ listId, initialItems }: ListDetailViewP
sortBy: newFilters.sortBy,
sortOrder: newFilters.sortOrder
})
- setFilteredItems(results as Item[])
+ // Extract items from pagination result
+ const items = 'items' in results ? results.items : results
+ setFilteredItems(items as Item[])
} catch (error) {
console.error('Filter error:', error)
setFilteredItems(initialItems)
diff --git a/components/SearchBar.tsx b/components/SearchBar.tsx
index a949bf3..57a03e1 100644
--- a/components/SearchBar.tsx
+++ b/components/SearchBar.tsx
@@ -1,5 +1,6 @@
'use client'
+import { memo } from 'react'
import { Search, X } from 'lucide-react'
import { useSearch } from '@/hooks'
@@ -10,7 +11,7 @@ interface SearchBarProps {
initialValue?: string
}
-export default function SearchBar({
+function SearchBar({
onSearch,
placeholder = 'Search tasks...',
debounceMs = 300,
@@ -53,3 +54,5 @@ export default function SearchBar({
)
}
+
+export default memo(SearchBar)
diff --git a/components/SearchResults.tsx b/components/SearchResults.tsx
index 13f6b01..9a52938 100644
--- a/components/SearchResults.tsx
+++ b/components/SearchResults.tsx
@@ -1,5 +1,6 @@
'use client'
+import { memo } from 'react'
import { highlightText } from '@/lib/utils'
import Link from 'next/link'
import { Calendar, Tag, AlertCircle } from 'lucide-react'
@@ -25,7 +26,7 @@ interface SearchResultsProps {
onClose?: () => void
}
-export default function SearchResults({ results, query, onClose }: SearchResultsProps) {
+function SearchResults({ results, query, onClose }: SearchResultsProps) {
if (!query.trim()) {
return null
}
@@ -155,3 +156,5 @@ export default function SearchResults({ results, query, onClose }: SearchResults
)
}
+
+export default memo(SearchResults)
diff --git a/lib/actions.ts b/lib/actions.ts
index 4435752..f15b8f3 100644
--- a/lib/actions.ts
+++ b/lib/actions.ts
@@ -525,6 +525,8 @@ export async function getItemsByFilter(
dueDateRange?: { start: Date | null; end: Date | null }
sortBy?: 'createdAt' | 'dueDate' | 'priority' | 'title'
sortOrder?: 'asc' | 'desc'
+ page?: number
+ pageSize?: number
} = {}
) {
try {
@@ -560,14 +562,26 @@ export async function getItemsByFilter(
}
}
- // Fetch items from database
- const items = await retryOperation(() =>
- prisma.item.findMany({
- where: whereClause,
- include: {
- list: true,
- },
- })
+ // Pagination parameters
+ const page = filters.page || 1
+ const pageSize = filters.pageSize || 50
+ const skip = (page - 1) * pageSize
+
+ // Fetch items from database with pagination
+ const [items, totalCount] = await retryOperation(() =>
+ Promise.all([
+ prisma.item.findMany({
+ where: whereClause,
+ include: {
+ list: true,
+ },
+ skip,
+ take: pageSize,
+ }),
+ prisma.item.count({
+ where: whereClause,
+ }),
+ ])
)
// Filter by tags (client-side since tags are stored as JSON)
@@ -618,7 +632,16 @@ export async function getItemsByFilter(
return sortOrder === 'asc' ? comparison : -comparison
})
- return sortedItems
+ return {
+ items: sortedItems,
+ pagination: {
+ page,
+ pageSize,
+ totalCount,
+ totalPages: Math.ceil(totalCount / pageSize),
+ hasMore: page * pageSize < totalCount,
+ },
+ }
} catch (error) {
console.error('Error filtering items:', error)
throw new Error('Failed to filter items. Please try again.')
diff --git a/next.config.js b/next.config.js
index a0501d7..9761814 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,8 +1,23 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
- serverActions: true,
+ optimizePackageImports: ['lucide-react'],
},
+ compiler: {
+ removeConsole: process.env.NODE_ENV === 'production' ? {
+ exclude: ['error', 'warn'],
+ } : false,
+ },
+ // Enable SWC minification for better performance
+ swcMinify: true,
+ // Optimize images
+ images: {
+ formats: ['image/avif', 'image/webp'],
+ },
+ // Enable compression
+ compress: true,
+ // Production source maps (smaller)
+ productionBrowserSourceMaps: false,
}
module.exports = nextConfig
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 8a9e93f..cd1fb37 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -29,4 +29,12 @@ model Item {
updatedAt DateTime @updatedAt
listId String
list List @relation(fields: [listId], references: [id], onDelete: Cascade)
+
+ @@index([listId])
+ @@index([completed])
+ @@index([priority])
+ @@index([dueDate])
+ @@index([createdAt])
+ @@index([listId, completed])
+ @@index([listId, priority])
}