UNPKG

oneie

Version:

Build apps, websites, and AI agents in English. Zero-interaction setup for AI agents (Claude Code, Cursor, Windsurf). Download to your computer, run in the cloud, deploy to the edge. Open source and free forever.

862 lines (686 loc) 18.5 kB
--- title: Improve dimension: things category: features tags: ai related_dimensions: connections, events scope: global created: 2025-11-03 updated: 2025-11-03 version: 1.0.0 ai_context: | This document is part of the things dimension in the features category. Location: one/things/features/improve.md Purpose: Documents astro + shadcn/ui template - comprehensive improvement plan Related dimensions: connections, events For AI agents: Read this to understand improve. --- # Astro + shadcn/ui Template - Comprehensive Improvement Plan ## Executive Summary This template represents an excellent foundation with modern technologies (Astro 5, React 19, Tailwind CSS v4, shadcn/ui). However, there are significant opportunities to enhance performance, SEO, accessibility, developer experience, and feature richness. This document provides a prioritized roadmap for improvements. --- ## 🔴 Critical Priority (Do First) ### 1. Performance Optimization **Current Issues:** - Using plain `<img>` tags instead of Astro's optimized `<Image>` component - Excessive `client:load` directives on static components (Badge, Calendar icons) - No lazy loading strategy - Missing font optimization **Improvements:** ```astro <!-- BEFORE: src/pages/blog/index.astro:104 --> <img src={entry.data.picture} alt={entry.data.title} class="w-full h-full object-cover" /> <!-- AFTER: Use Astro's Image component --> import {Image} from 'astro:assets'; <Image src={entry.data.picture} alt={entry.data.title} width={800} height={450} loading="lazy" class="w-full h-full object-cover" /> ``` ```astro <!-- BEFORE: Unnecessary hydration --> <Badge variant="secondary" client:load>{entry.data.type}</Badge> <Calendar className="w-4 h-4 mr-1" client:load /> <!-- AFTER: Static rendering --> <Badge variant="secondary">{entry.data.type}</Badge> <Calendar className="w-4 h-4 mr-1" /> ``` **Font Optimization:** ```astro <!-- Add to Layout.astro head --> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preload" as="font" href="/fonts/font-name.woff2" type="font/woff2" crossorigin /> ``` **Expected Impact:** - 30-50% reduction in JavaScript bundle size - 2-3x faster image loading - Improved Lighthouse performance score from ~85 to 95+ --- ### 2. SEO Enhancement **Missing Critical SEO Elements:** ```astro <!-- Add to Layout.astro:21-28 --> <meta property="og:title" content={title} /> <meta property="og:description" content={description} /> <meta property="og:type" content="website" /> <meta property="og:url" content={Astro.url} /> <meta property="og:image" content={new URL('/og.jpg', Astro.url)} /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" content={title} /> <meta name="twitter:description" content={description} /> <link rel="canonical" href={Astro.url.pathname} /> ``` **Generate Sitemap:** ```typescript // astro.config.mjs import sitemap from "@astrojs/sitemap"; export default defineConfig({ site: "https://your-domain.com", integrations: [react(), sitemap()], }); ``` **Add RSS Feed:** ```typescript // src/pages/rss.xml.ts import rss from "@astrojs/rss"; import { getCollection } from "astro:content"; export async function GET(context) { const blog = await getCollection("blog"); return rss({ title: "Your Blog", description: "A beautiful blog", site: context.site, items: blog.map((post) => ({ title: post.data.title, pubDate: post.data.date, description: post.data.description, link: `/blog/${post.slug}/`, })), }); } ``` **Add Structured Data:** ```astro <!-- src/pages/blog/[...slug].astro --> <script type="application/ld+json" set:html={JSON.stringify({ '@context': 'https://schema.org', '@type': 'BlogPosting', headline: entry.data.title, datePublished: entry.data.date, description: entry.data.description, author: { '@type': 'Person', name: 'Your Name' }, })} /> ``` --- ### 3. Accessibility Improvements **Critical Issues:** - No skip-to-content link - Missing ARIA labels - Keyboard navigation incomplete - Missing focus indicators **Fixes:** ```astro <!-- Add skip link to Layout.astro:32 --> <a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-foreground" > Skip to main content </a> <!-- Add id to main --> <main id="main-content" class="flex-1 p-6"></main> ``` ```tsx // src/components/Sidebar.tsx - Add ARIA labels <nav aria-label="Main navigation" className="flex-1 pl-1"> {navigation.map((item) => ( <a aria-label={item.title} aria-current={activePath === item.path ? 'page' : undefined} // ... rest of props > ``` **Focus Indicators (add to global.css):** ```css /* Enhanced focus styles */ *:focus-visible { outline: 2px solid hsl(var(--color-ring)); outline-offset: 2px; } /* Skip link styles */ .sr-only:not(:focus):not(:active) { clip: rect(0 0 0 0); clip-path: inset(50%); height: 1px; overflow: hidden; position: absolute; white-space: nowrap; width: 1px; } ``` --- ## 🟡 High Priority ### 4. Content Collections Enhancement **Improve Blog Schema:** ```typescript // src/content/config.ts const BlogSchema = z.object({ title: z.string(), description: z.string(), date: z.date(), draft: z.boolean().default(false), image: z.string().optional(), // Remove duplicate picture/image author: z.string().default("Default Author"), tags: z.array(z.string()).default([]), category: z.enum(["tutorial", "news", "guide", "review"]).default("tutorial"), readingTime: z.number().optional(), // Auto-calculate or manual featured: z.boolean().default(false), relatedPosts: z.array(z.string()).optional(), // Slugs of related posts }); ``` **Add Reading Time Calculation:** ```typescript // src/lib/reading-time.ts export function calculateReadingTime(content: string): number { const wordsPerMinute = 200; const wordCount = content.trim().split(/\s+/).length; return Math.ceil(wordCount / wordsPerMinute); } ``` --- ### 5. Blog Features Enhancement **Add Pagination:** ```astro --- // src/pages/blog/[page].astro export async function getStaticPaths({ paginate }) { const posts = await getCollection('blog'); return paginate(posts, { pageSize: 12 }); } const { page } = Astro.props; --- <!-- Pagination UI --> <div class="flex gap-2 justify-center mt-8"> {page.url.prev && <a href={page.url.prev}>Previous</a>} <span>Page {page.currentPage} of {page.lastPage}</span> {page.url.next && <a href={page.url.next}>Next</a>} </div> ``` **Add Tag Filtering:** ```astro --- // src/pages/blog/tag/[tag].astro export async function getStaticPaths() { const posts = await getCollection('blog'); const tags = [...new Set(posts.flatMap((post) => post.data.tags))]; return tags.map((tag) => ({ params: { tag }, props: { posts: posts.filter((post) => post.data.tags.includes(tag)), tag, }, })); } --- ``` **Add Search Functionality:** ```typescript // src/components/BlogSearch.tsx import { useState, useMemo } from 'react'; import { Input } from '@/components/ui/input'; export function BlogSearch({ posts }) { const [query, setQuery] = useState(''); const filtered = useMemo(() => posts.filter(post => post.data.title.toLowerCase().includes(query.toLowerCase()) || post.data.description.toLowerCase().includes(query.toLowerCase()) ), [query, posts] ); return ( <div> <Input type="search" placeholder="Search posts..." value={query} onChange={(e) => setQuery(e.target.value)} /> {/* Render filtered posts */} </div> ); } ``` **Add Table of Contents:** ```typescript // src/components/TableOfContents.tsx interface TOCProps { headings: { depth: number; slug: string; text: string; }[]; } export function TableOfContents({ headings }: TOCProps) { return ( <nav className="sticky top-4"> <h3 className="font-semibold mb-2">On this page</h3> <ul className="space-y-1"> {headings.map(h => ( <li key={h.slug} style={{ paddingLeft: `${(h.depth - 2) * 1}rem` }}> <a href={`#${h.slug}`} className="text-sm hover:text-primary"> {h.text} </a> </li> ))} </ul> </nav> ); } ``` --- ### 6. Error Handling & Edge Cases **Add 404 Page:** ```astro --- // src/pages/404.astro import Layout from '@/layouts/Layout.astro'; import { Button } from '@/components/ui/button'; --- <Layout title="404 - Page Not Found"> <div class="flex flex-col items-center justify-center min-h-[60vh] text-center" > <h1 class="text-6xl font-bold mb-4">404</h1> <p class="text-xl text-muted-foreground mb-6"> Oops! The page you're looking for doesn't exist. </p> <Button client:load> <a href="/">Go Home</a> </Button> </div> </Layout> ``` **Add Error Boundary:** ```tsx // src/components/ErrorBoundary.tsx import { Component, type ReactNode } from "react"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; interface Props { children: ReactNode; } interface State { hasError: boolean; error?: Error; } export class ErrorBoundary extends Component<Props, State> { state: State = { hasError: false }; static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; } render() { if (this.state.hasError) { return ( <Alert variant="destructive"> <AlertTitle>Something went wrong</AlertTitle> <AlertDescription> {this.state.error?.message || "An unexpected error occurred"} </AlertDescription> </Alert> ); } return this.props.children; } } ``` --- ## 🟢 Medium Priority ### 7. Developer Experience **Add ESLint & Prettier:** ```json // .eslintrc.json { "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:astro/recommended" ], "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint"], "overrides": [ { "files": ["*.astro"], "parser": "astro-eslint-parser", "parserOptions": { "parser": "@typescript-eslint/parser", "extraFileExtensions": [".astro"] } } ] } ``` ```json // .prettierrc { "semi": true, "singleQuote": true, "tabWidth": 2, "trailingComma": "es5", "printWidth": 100, "plugins": ["prettier-plugin-astro"], "overrides": [ { "files": "*.astro", "options": { "parser": "astro" } } ] } ``` **Add Git Hooks:** ```bash pnpm add -D husky lint-staged npx husky init echo "pnpm lint-staged" > .husky/pre-commit ``` ```json // package.json { "lint-staged": { "*.{ts,tsx,astro}": ["eslint --fix", "prettier --write"], "*.{json,md}": ["prettier --write"] } } ``` **Add Testing Setup:** ```typescript // vitest.config.ts import { defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], test: { environment: "jsdom", setupFiles: ["./src/test/setup.ts"], }, }); ``` **VS Code Settings:** ```json // .vscode/settings.json { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "[astro]": { "editor.defaultFormatter": "astro-build.astro-vscode" }, "tailwindCSS.experimental.classRegex": [ ["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] ] } ``` --- ### 8. Mobile Optimization **Improve Mobile Sidebar:** ```tsx // src/components/Sidebar.tsx - Add mobile version const MobileSidebar = () => { const [isOpen, setIsOpen] = useState(false); return ( <> <button onClick={() => setIsOpen(true)} className="fixed top-4 left-4 z-50 md:hidden" aria-label="Open menu" > <Menu className="h-6 w-6" /> </button> <Sheet open={isOpen} onOpenChange={setIsOpen}> <SheetContent side="left" className="w-64"> <nav className="space-y-2">{/* Navigation items */}</nav> </SheetContent> </Sheet> </> ); }; ``` **Touch Gestures:** ```typescript // Add swipe-to-close for mobile sidebar import { useSwipeable } from "react-swipeable"; const handlers = useSwipeable({ onSwipedLeft: () => setIsOpen(false), trackMouse: false, }); ``` --- ### 9. Architecture Improvements **Environment Variable Validation:** ```typescript // src/env.ts import { z } from "zod"; const envSchema = z.object({ PUBLIC_SITE_URL: z.string().url(), PUBLIC_ANALYTICS_ID: z.string().optional(), GITHUB_TOKEN: z.string().optional(), }); export const env = envSchema.parse(import.meta.env); ``` **Remove Unused Path Alias:** ```json // tsconfig.json - Remove this line: "@1/*": ["src/1/*"], // REMOVE ``` **Make Navigation Configurable:** ```typescript // src/config/navigation.ts export const siteConfig = { name: "Your Site", navigation: [ { title: "Home", path: "/", icon: "Home" }, { title: "Blog", path: "/blog", icon: "Book" }, // ... more items ], social: { github: "https://github.com/yourusername", twitter: "https://twitter.com/yourusername", }, }; ``` --- ## 🔵 Low Priority (Nice to Have) ### 10. Advanced Features **Dark Mode Transition Animation:** ```css /* global.css */ ::view-transition-old(root), ::view-transition-new(root) { animation: none; mix-blend-mode: normal; } .dark::view-transition-old(root) { z-index: 1; } .dark::view-transition-new(root) { z-index: 999; } ``` **Social Sharing:** ```tsx // src/components/ShareButtons.tsx export function ShareButtons({ title, url }) { const shareData = { title, url: typeof window !== "undefined" ? window.location.href : url, }; return ( <div className="flex gap-2"> <button onClick={() => navigator.share?.(shareData)}>Share</button> <a href={`https://twitter.com/intent/tweet?text=${title}&url=${url}`}> Twitter </a> <a href={`https://www.facebook.com/sharer/sharer.php?u=${url}`}> Facebook </a> </div> ); } ``` **Analytics Integration:** ```astro --- const { analyticsId } = Astro.props; --- <!-- src/components/Analytics.astro -->{ analyticsId && ( <script is:inline define:vars={{ analyticsId }}> // Google Analytics or Plausible code </script> ) } ``` **View Transitions:** ```typescript // astro.config.mjs export default defineConfig({ experimental: { viewTransitions: true, }, }); ``` ```astro <!-- Layout.astro -->import {ViewTransitions} from 'astro:transitions'; <head> <ViewTransitions /> </head> ``` --- ### 11. Security Enhancements **Content Security Policy:** ```typescript // src/middleware.ts export const onRequest = async (context, next) => { const response = await next(); response.headers.set( "Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';", ); response.headers.set("X-Frame-Options", "DENY"); response.headers.set("X-Content-Type-Options", "nosniff"); response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin"); return response; }; ``` --- ### 12. Documentation Improvements **Component Documentation:** ````tsx /** * Sidebar component with expandable navigation * * @example * ```tsx * <Sidebar client:load /> * ``` * * @remarks * - Expands on hover (desktop) * - Sheet drawer on mobile * - Auto-detects active route */ export function Sidebar() { ... } ```` **README Enhancement:** ```markdown ## 🚀 Quick Start \`\`\`bash # Clone the repository git clone https://github.com/your-repo/astro-shadcn # Install dependencies pnpm install # Start dev server pnpm dev # Build for production pnpm build \`\`\` ## 📝 Adding Blog Posts Create a new `.md` file in `src/content/blog/`: ## \`\`\`markdown title: "My Post" description: "Description" date: 2025-09-30 tags: ["astro", "react"] --- Your content here... \`\`\` ``` --- ## Implementation Roadmap ### Week 1: Critical Fixes - [ ] Replace `<img>` with `<Image>` components - [ ] Remove unnecessary `client:load` directives - [ ] Add SEO meta tags (OG, Twitter Cards) - [ ] Implement skip-to-content link - [ ] Add ARIA labels ### Week 2: Content Enhancement - [ ] Improve blog schema (tags, categories, reading time) - [ ] Add pagination to blog - [ ] Implement tag filtering - [ ] Create 404 page - [ ] Add error boundary ### Week 3: Developer Experience - [ ] Setup ESLint + Prettier - [ ] Add git hooks (husky) - [ ] Configure VS Code settings - [ ] Add basic testing setup ### Week 4: Features & Polish - [ ] Add search functionality - [ ] Implement table of contents - [ ] Improve mobile sidebar - [ ] Add social sharing buttons - [ ] Generate sitemap and RSS feed ### Week 5: Advanced Features - [ ] Add analytics integration - [ ] Implement view transitions - [ ] Add security headers - [ ] Performance audit and optimization - [ ] Documentation improvements --- ## Metrics & Goals ### Performance Targets - Lighthouse Performance: 95+ (currently ~85) - First Contentful Paint: < 1.2s - Time to Interactive: < 2.5s - Total Bundle Size: < 200KB (currently ~350KB) ### SEO Targets - Lighthouse SEO: 100 (currently ~85) - All pages have unique meta descriptions - Sitemap covering all pages - Valid structured data on all content pages ### Accessibility Targets - Lighthouse Accessibility: 100 (currently ~90) - WCAG 2.1 AA compliance - Keyboard navigation for all interactive elements - Screen reader tested --- ## Conclusion This template has an excellent foundation, but implementing these improvements will transform it into a production-ready, enterprise-grade solution. Prioritize the critical and high-priority items first, as they provide the most value for effort invested. The key areas requiring immediate attention are: 1. **Performance** - Remove unnecessary hydration, optimize images 2. **SEO** - Add meta tags, sitemap, RSS feed 3. **Accessibility** - ARIA labels, keyboard navigation, focus management 4. **Content Features** - Pagination, tags, search By following this roadmap, you'll have a template that not only looks beautiful but performs exceptionally well in real-world production scenarios.