Building a personal website seems straightforward at first—until you start making technical decisions. Should you use a traditional CMS? A static site generator? What about content storage, styling approach, deployment strategy? As someone who's walked this path recently with Lilyslab, I want to share my architecture decisions and the thinking behind them.
Technology Stack Selection
The Framework Decision: Next.js
When I began planning Lilyslab, I evaluated several options:
- Traditional CMS (WordPress, Ghost): Great content management, but less developer flexibility
- Static Site Generators (11ty, Hugo): Lightning fast, but limited dynamic features
- JavaScript Frameworks (React, Vue): Flexible but require more infrastructure decisions
- Meta-frameworks (Next.js, Remix, Astro): Combines React with built-in optimization
I chose Next.js with the App Router for several reasons:
- Hybrid Rendering: I wanted both static content (writings, notes) and dynamic features (comments, analytics)
- React Ecosystem: Leveraging my existing knowledge and the component ecosystem
- Built-in Optimizations: Image handling, route pre-fetching, and code splitting
- Server Components: Reducing client-side JavaScript while maintaining interactivity where needed
- Simplified API Routes: For future features like newsletter subscriptions
The App Router specifically offered better file-based routing and simplified data fetching patterns, which made organizing the various sections of my digital garden more intuitive.
Content Management: The Filesystem Approach
Rather than using a headless CMS, I decided to store content in Markdown files within the codebase:
Content/
├── writings/ # Long-form articles
├── notes/ # Quick thoughts
├── bookmarks/ # Interesting links
├── logs/ # Changelog entries
└── resources/ # Learning materials
This approach has several benefits:
- Git Version Control: Every content change is versioned
- No External Dependencies: No database or API to maintain
- Local Editing: I can use Obsidian as my primary editor
- Content Portability: Easy to migrate if I change platforms
I use a combination of fs
and path
modules to read directories and files, and gray-matter
to parse frontmatter. Each content type has a standardized schema that I validate using Zod.
Architecture Patterns
Separation of Concerns
I've organized the codebase into clear domains:
app/ # Application routes and pages
├── api/ # API endpoints
├── digital-garden/ # Garden section routes
├── workshop/ # Workshop section routes
components/ # Shared React components
lib/ # Utilities and helpers
├── types/ # TypeScript definitions
├── config/ # Configuration
└── utils/ # Helper functions
Content/ # All content files
public/ # Static assets
This structure allows me to:
- Separate presentation from business logic
- Reuse components across different sections
- Centralize content fetching and processing
Content Modules
For each content type, I've created a dedicated module that handles:
- Fetching: Reading files from the filesystem
- Parsing: Converting markdown to HTML
- Validation: Ensuring content follows the expected schema
- Transformation: Preparing data for display
This pattern makes it easy to add new content types or modify existing ones without touching the presentation layer.
Styling Approach
I use Tailwind CSS with a custom theme, which gives me:
- Rapid Iteration: Fast styling without context switching
- Consistent Design: Theme variables enforce a cohesive look
- Dark Mode Support: Built-in with minimal effort
- Small Bundle Size: Only includes CSS I actually use
For complex components, I combine Tailwind with shadcn/ui, a collection of unstyled, accessible components that I can customize. This gives me the design control I want without reinventing complex behaviors like modals, dropdowns, and form controls.
Key Technical Decisions
MDX over Plain Markdown
I chose MDX (Markdown with JSX) to allow:
- Interactive Examples: Embedding React components in content
- Custom Components: Creating specialized displays for code, quotes, etc.
- Dynamic Content: Adding visualizations and special layouts
TypeScript for Type Safety
TypeScript has been essential for:
- Content Validation: Creating interfaces for each content type
- Refactoring Confidence: Changing code structure with safety
- Better Developer Experience: Autocomplete and documentation
It did lead to some challenges with dynamic route types, but the benefits far outweighed the costs.
Client-Side vs. Server Components
I carefully consider which components need interactivity:
- Server Components: Content display, navigation, static elements
- Client Components: Interactive filters, search, theme toggles
This "server-first" approach significantly reduces the JavaScript sent to browsers.
Deployment and Hosting
I deploy Lilyslab to Vercel because:
- Next.js Integration: Perfect pairing with my framework
- Preview Deployments: Every PR gets a live preview
- Edge Functions: API routes run closer to users
- Analytics: Built-in web vitals tracking
The site is set to rebuild daily, ensuring fresh content without manual deploys.
Reflections and Lessons
What Worked Well
- File-based Content: Simple, portable, and version-controlled
- Component Modularity: Easy to add new sections and features
- Type Safety: Caught many bugs during development
What I'd Reconsider
- Metadata Handling: Setting up consistent metadata was trickier than expected
- Image Management: Next.js image optimization is powerful but required more configuration than anticipated
- State Management: For simpler features, prop drilling would have been sufficient
Future Architecture Plans
As Lilyslab grows, I'm planning several architectural improvements:
- Content Search: Adding vector search for semantic queries
- Interactive Components: More dynamic elements in the digital garden
- Automated Testing: Expanding test coverage for critical paths
- Performance Optimization: Further reducing bundle sizes and load times
Conclusion
Architecture isn't just about technical decisions—it's about creating systems that support your goals and workflow. For my digital garden and digital workshop, this architecture provides the right balance of flexibility, performance, and maintainability.
Every project has unique requirements, so your choices may differ. The important thing is to make deliberate decisions based on your specific needs rather than blindly following trends.
If you're building your own digital space, I hope these insights help you navigate your own architectural decisions.
Have questions about my approach or want to discuss web architecture? Feel free to reach out—I'd love to exchange ideas.