placebo.mk/ROUTER_TAILWIND_SETUP.md
2026-01-10 19:41:04 +01:00

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 createRouter with route tree

2. Tailwind CSS 4 (CSS-First)

  • Configuration: In src/styles.css using @import and @theme directives
  • Vite integration: tailwindcss() plugin in vite.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:

Adding New Routes

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])
import { Link } from '@tanstack/react-router'

// In your component
<Link to="/article/123">Read Article</Link>

Adding Shadcn/ui Components

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

  1. Check that route files are in src/routes/
  2. Check export: export const Route = createFileRoute(...)
  3. Check imports in src/main.tsx
  4. Restart dev server

Styles Not Applying

  1. Check src/styles.css has @import "tailwindcss"
  2. Check vite.config.ts has tailwindcss() plugin
  3. Restart dev server
  4. Hard refresh browser (Ctrl+Shift+R)

Component Imports Not Working

  1. Check tsconfig.json has @/ paths configured
  2. Check vite.config.ts has resolve alias for @/
  3. Restart TypeScript server (VSCode: Cmd+Shift+P → "TypeScript: Restart TS Server")

TanStack Query Not Fetching

  1. Check backend is running: http://localhost:3000
  2. Check .env has correct API URL
  3. Check browser network tab for failed requests
  4. 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 defer for non-critical resources

5. SEO

  • Add meta tags to root route
  • Add Open Graph tags
  • Implement structured data

Next Steps

  1. Restart dev server to load new configuration
  2. Visit home page: http://localhost:5173/
  3. Visit articles page: http://localhost:5173/articles
  4. Add article detail page with $id parameter
  5. Add shadcn/ui components as needed
  6. Add dark mode toggle
  7. Add search functionality
  8. Add category/tag filtering

Resources