A modern, performant CMS built with Next.js 15, TypeScript, and Tailwind CSS.
- ✅ React Query for efficient data fetching and caching
- ✅ Next/Image for optimized image loading
- ✅ Suspense boundaries for better loading states
- ✅ Error boundaries for graceful error handling
- ✅ Code splitting and lazy loading
- ✅ Production-safe logging (auto-strips in production)
- ✅ TypeScript strict mode enabled
- ✅ Zod validators for all API payloads
- ✅ Type-safe API client with proper error handling
- ✅ ESLint rules enforcing best practices
- ✅ Custom hooks for all data operations
- ✅ Centralized configuration management
- ✅ Environment validation at startup
- ✅ React Query Devtools for debugging
- ✅ Comprehensive error handling
- ✅ Loading skeletons for all async data
- ✅ Toast notifications (ready to implement)
- ✅ Responsive design with Tailwind CSS
- ✅ Dark/B&W themes with proper CSS variables
- ✅ Accessible components (Radix UI)
stv-cms-nextjs/
├── app/ # Next.js App Router
│ ├── layout.tsx # Root layout with providers
│ ├── page.tsx # Dashboard page
│ └── post/ # Post-related routes
├── src/
│ ├── components/ # React components
│ │ ├── ui/ # shadcn/ui components
│ │ ├── providers/ # Context providers
│ │ ├── error-boundary.tsx
│ │ ├── loading-skeletons.tsx
│ │ └── cms-image.tsx
│ ├── config/ # App configuration
│ │ └── index.ts
│ ├── hooks/ # Custom React hooks
│ │ ├── use-posts.ts
│ │ ├── use-post-queries.ts
│ │ ├── use-image-upload.ts
│ │ └── use-utils.ts
│ ├── lib/ # Utilities and API
│ │ ├── api.ts
│ │ ├── api-errors.ts
│ │ ├── logger.ts
│ │ ├── env.ts
│ │ ├── query-keys.ts
│ │ └── utils.ts
│ ├── types/ # TypeScript definitions
│ │ └── post.ts
│ └── validators/ # Zod schemas
│ └── post.ts
└── public/ # Static assets
- Node.js 18+
- npm or yarn
- Backend API running on port 8080 (or configure in
.env)
- Clone and install dependencies
npm install- Set up environment variables
cp .env.example .env
# Edit .env with your configuration- Run development server
npm run dev- Build for production
npm run build
npm startServer Component (Recommended for initial load)
import { getPosts } from '@/lib/api';
export default async function PostsPage() {
const posts = await getPosts();
return <PostsList posts={posts} />;
}Client Component (with React Query)
'use client';
import { usePosts } from '@/hooks/use-post-queries';
import { PostCardSkeleton } from '@/components/loading-skeletons';
export default function PostsPage() {
const { data: posts, isLoading, error } = usePosts();
if (isLoading) return <PostCardSkeleton />;
if (error) return <ErrorDisplay error={error} />;
return <PostsList posts={posts} />;
}'use client';
import { useCreatePost } from '@/hooks/use-post-queries';
import { useRouter } from 'next/navigation';
export default function CreatePostForm() {
const mutation = useCreatePost();
const router = useRouter();
const handleSubmit = async (data: FormData) => {
try {
await mutation.mutateAsync({
title: data.get('title') as string,
user_id: 'Stiven Valeriano',
content_blocks: [...],
});
router.push('/');
} catch (error) {
console.error('Failed to create post:', error);
}
};
return <form onSubmit={handleSubmit}>...</form>;
}'use client';
import { useImageUpload } from '@/hooks/use-image-upload';
import { CMSImage } from '@/components/cms-image';
export default function ImageUploader() {
const { upload, loading, previewUrl, filename } = useImageUpload();
return (
<div>
<input type="file" onChange={(e) => upload(e.target.files[0])} />
{loading && <p>Uploading...</p>}
{previewUrl && <img src={previewUrl} alt="Preview" />}
{filename && <CMSImage src={filename} alt="Uploaded" width={400} height={300} />}
</div>
);
}| Variable | Description | Default |
|---|---|---|
API_URL |
Backend API URL (server-side) | http://localhost:8080 |
NEXT_PUBLIC_IMAGE_URL |
Image base URL | http://localhost:8080 |
API_TIMEOUT |
Request timeout (ms) | 10000 |
API_RETRY_ATTEMPTS |
Retry attempts | 3 |
NEXT_PUBLIC_APP_NAME |
App name | STV CMS |
DEBUG |
Enable debug logging | false |
Edit tailwind.config.ts to customize:
- Colors and design tokens
- Breakpoints
- Animations
- Component variants
The app supports multiple themes via CSS variables:
- Dark (default): Modern dark theme
- B&W: Black and white minimal theme
Switch themes using the theme provider or UI toggle.
- Use Server Components for initial data fetching
- Use React Query hooks for client-side interactions
- Enable image optimization with
CMSImagecomponent - Implement route prefetching with
Linkcomponent - Use Suspense boundaries for better loading UX
# Run linter
npm run lint
# Type check
npx tsc --noEmit
# Build
npm run build- Use Server Components for data fetching
- Use React Query for client-side mutations
- Implement loading skeletons
- Handle errors with ErrorBoundary
- Use TypeScript strictly
- Validate inputs with Zod
- Don't use
console.log(uselogger) - Don't skip error handling
- Don't use
anytype - Don't hardcode URLs
- Don't ignore loading states
- Don't bypass TypeScript
All API errors are properly typed:
import { isApiError, getErrorMessage } from '@/lib/api-errors';
try {
await createPost(data);
} catch (error) {
if (isApiError(error)) {
// Handle API-specific errors
console.error('API Error:', error.message, error.status);
} else {
// Handle other errors
console.error('Unexpected error:', getErrorMessage(error));
}
}- Follow the existing code structure
- Use TypeScript strictly
- Add proper error handling
- Update documentation
- Test before committing
MIT License
- Add unit tests
- Implement form validation with react-hook-form
- Add optimistic updates
- Implement search with debounce
- Add pagination component
- Implement drag-and-drop for images
- Add keyboard shortcuts
- Improve accessibility (ARIA labels)
- Add analytics tracking
- Implement PWA features
Built with ❤️ using Next.js 15, TypeScript, and Tailwind CSS