9.5 KiB
9.5 KiB
TanStack Router + Tailwind 4 Setup Complete ✅
What's Been Configured
1. TanStack Router with File-Based Routing
- Routes directory:
frontend/src/routes/ - Root route:
root.tsx- Layout with header, footer, and outlet - Home route:
index.tsx- Welcome page with getting started cards - Articles route:
articles.tsx- Article list with card grid - Router configuration: Uses
createRouterwith route tree
2. Tailwind CSS 4 (CSS-First)
- Configuration: In
src/styles.cssusing@importand@themedirectives - Vite integration:
tailwindcss()plugin invite.config.ts - No config file needed: Tailwind 4 CSS-first doesn't use
tailwind.config.js - Shadcn/ui compatible: CSS variables for theming
3. Routing Structure
frontend/src/routes/
├── root.tsx # Main layout (header, footer, <Outlet />)
├── index.tsx # Home page (/)
└── articles.tsx # Articles list (/articles)
4. Page Templates
Root Layout (root.tsx)
- Header with navigation (Home, Articles)
- Main content area with
<Outlet /> - Footer with copyright
- Meta tags for SEO
- CSS import for Tailwind
Home Page (index.tsx)
- Welcome cards with:
- "Welcome" - Introduction
- "Get Started" - Feature list
- Centered layout with max-width container
Articles Page (articles.tsx)
- Grid of article cards (responsive: 1/2/3 columns)
- Loading state
- Error state
- Empty state (no articles)
- Each card shows:
- Featured image (if available)
- Title (truncated to 2 lines)
- Excerpt (truncated to 3 lines)
- Content preview (truncated to 4 lines)
- Date and view count
How It Works
1. Route Configuration
// src/routes/root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
export const rootRoute = createRootRoute({
head: () => ({
meta: [
{
title: 'Placebo.mk - Sarcastic News from Macedonia',
description: 'Latest news and articles from Macedonia with a sarcastic twist',
},
],
links: [{ rel: 'stylesheet', href: '../../styles.css' }],
}),
component: () => (
<div>
<header>...</header>
<main><Outlet /></main>
<footer>...</footer>
</div>
),
})
2. Route Tree
// src/main.tsx
import { createRouter } from '@tanstack/react-router'
import { rootRoute } from './routes/root'
import { indexRoute } from './routes/index'
import { articlesRoute } from './routes/articles'
const routeTree = rootRoute.addChildren([indexRoute, articlesRoute])
const router = createRouter({ routeTree, context: { queryClient } })
3. File-Based Routes
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: () => <div>Home</div>,
})
// src/routes/articles.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/articles')({
component: () => <div>Articles</div>,
})
4. Tailwind 4 CSS-First Setup
In src/styles.css:
@import "tailwindcss";
@theme {
--color-primary: oklch(0.647 0.22 0.23);
--font-sans: "Inter", sans-serif;
}
In vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from 'tailwindcss'
import path from 'path'
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})
Running the App
cd frontend
npm run dev
Visit:
- Home: http://localhost:5173/
- Articles: http://localhost:5173/articles
Adding New Routes
Method 1: File-Based Routes (Recommended)
Create a new file in src/routes/:
// src/routes/article-detail.tsx
import { createFileRoute } from '@tanstack/react-router'
import { useQuery } from '@tanstack/react-query'
import * as api from '../lib/api'
export const Route = createFileRoute('/articles/$id')({
component: () => {
const { id } = Route.useParams()
const { data } = useQuery({
queryKey: ['article', id],
queryFn: () => api.fetchArticleById(id),
})
return (
<div>
<h1>{data?.title}</h1>
<div dangerouslySetInnerHTML={{ __html: data?.content || '' }} />
</div>
)
},
})
Then add to src/main.tsx:
import { articleDetailRoute } from './routes/article-detail'
const routeTree = rootRoute.addChildren([indexRoute, articlesRoute, articleDetailRoute])
Method 2: Using Link for Navigation
import { Link } from '@tanstack/react-router'
// In your component
<Link to="/article/123">Read Article</Link>
Adding Shadcn/ui Components
Using the CLI (Recommended)
cd frontend
npx shadcn@latest add [component-name]
Common Components to Add
npx shadcn@latest add button # (already installed)
npx shadcn@latest add badge # For article tags
npx shadcn@latest add input # For search
npx shadcn@latest add dialog # For article details
npx shadcn@latest add dropdown-menu
npx shadcn@latest add select
npx shadcn@latest add tabs # For content organization
npx shadcn@latest add separator # For visual separation
npx shadcn@latest add skeleton # For loading states
npx shadcn@latest add pagination # For article list navigation
Using Components in Routes
import { Button } from '@/components/ui/button'
export const Route = createFileRoute('/articles')({
component: () => (
<div>
<Button variant="default">Click me</Button>
</div>
),
})
Customization
Theme Colors
Edit src/styles.css:
@theme {
--color-primary: oklch(0.647 0.22 0.23);
--color-secondary: oklch(0.8 0.15 0.1);
--font-sans: "Your Font", sans-serif;
}
Adding Fonts
Edit src/styles.css:
@theme {
--font-sans: "Inter", sans-serif;
}
@layer base {
* {
@apply font-sans;
}
}
Dark Mode
Add dark mode toggle to header:
import { useEffect } from 'react'
export const rootRoute = createRootRoute({
component: () => {
const toggleDarkMode = () => {
document.documentElement.classList.toggle('dark')
localStorage.setItem('theme',
document.documentElement.classList.contains('dark') ? 'dark' : 'light'
)
}
return (
<div>
<header>
<button onClick={toggleDarkMode}>
Toggle Theme
</button>
</header>
<main><Outlet /></main>
</div>
)
},
})
File Structure
frontend/
├── src/
│ ├── components/
│ │ └── ui/ # shadcn/ui components
│ ├── lib/
│ │ ├── api.ts # Backend API functions
│ │ ├── query-client.ts # TanStack Query configuration
│ │ └── utils.ts # cn() utility (for shadcn/ui)
│ ├── routes/ # TanStack Router routes
│ │ ├── root.tsx # Root layout
│ │ ├── index.tsx # Home page
│ │ └── articles.tsx # Articles list
│ ├── styles.css # Tailwind CSS + theme
│ └── main.tsx # App entry point
├── components.json # shadcn/ui configuration
├── vite.config.ts # Vite + Tailwind CSS plugin
├── tailwind.config.js # (Not used - CSS-first approach)
└── package.json
Troubleshooting
Routes Not Working
- Check that route files are in
src/routes/ - Check export:
export const Route = createFileRoute(...) - Check imports in
src/main.tsx - Restart dev server
Styles Not Applying
- Check
src/styles.csshas@import "tailwindcss" - Check
vite.config.tshastailwindcss()plugin - Restart dev server
- Hard refresh browser (Ctrl+Shift+R)
Component Imports Not Working
- Check
tsconfig.jsonhas@/paths configured - Check
vite.config.tshas resolve alias for@/ - Restart TypeScript server (VSCode: Cmd+Shift+P → "TypeScript: Restart TS Server")
TanStack Query Not Fetching
- Check backend is running:
http://localhost:3000 - Check
.envhas correct API URL - Check browser network tab for failed requests
- Check CORS configuration in backend
Best Practices
1. Route Organization
- Keep routes in
src/routes/directory - Use file-based routing for simple pages
- Use nested routes for related content
2. Component Reusability
- Create reusable components in
src/components/ - Use shadcn/ui for consistent styling
- Extract common patterns (loading states, error states)
3. Data Fetching
- Use TanStack Query for all server state
- Implement proper loading and error states
- Use query keys consistently
4. Performance
- Use
lazy()for code splitting large components - Implement image lazy loading
- Use
deferfor non-critical resources
5. SEO
- Add meta tags to root route
- Add Open Graph tags
- Implement structured data
Next Steps
- Restart dev server to load new configuration
- Visit home page: http://localhost:5173/
- Visit articles page: http://localhost:5173/articles
- Add article detail page with
$idparameter - Add shadcn/ui components as needed
- Add dark mode toggle
- Add search functionality
- Add category/tag filtering