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