This document describes the architecture, implementation details, and technical decisions for the Foliox portfolio generator.
Foliox is built with:
- Next.js 16 with App Router for server-side rendering and API routes
- TypeScript for type safety and better developer experience
- Vercel AI SDK with Groq provider for AI-powered content generation
- GitHub GraphQL API for efficient data fetching
- PostgreSQL with Prisma ORM for database operations
- Database-backed caching for performance optimization
- Next.js middleware for API key authentication and CORS
foliox/
├── app/
│ ├── (portfolio)/
│ │ └── [username]/
│ │ └── page.tsx # Dynamic portfolio pages with custom URL support
│ ├── api/
│ │ ├── custom-url/
│ │ │ ├── check/route.ts # POST /api/custom-url/check
│ │ │ └── register/route.ts # POST /api/custom-url/register
│ │ ├── user/
│ │ │ └── [username]/
│ │ │ ├── profile/route.ts # GET /api/user/:username/profile
│ │ │ ├── projects/route.ts # GET /api/user/:username/projects
│ │ │ ├── about/route.ts # GET /api/user/:username/about
│ │ │ ├── contributions/route.ts # GET /api/user/:username/contributions
│ │ │ └── prs-by-org/route.ts # GET /api/user/:username/prs-by-org
│ │ └── linkedin/
│ │ └── [username]/route.ts # GET /api/linkedin/:username
│ │ └── screenshot/
│ │ └── route.ts # GET /api/screenshot
│ ├── layout.tsx
│ └── page.tsx # Landing page
├── components/
│ ├── portfolio/ # Portfolio-specific components
│ │ ├── hero-section.tsx
│ │ ├── share-button.tsx # Share dialog with custom URL creation
│ │ ├── projects-section.tsx
│ │ ├── project-image.tsx # Client component for project screenshots with fallback
│ │ └── ...
│ └── ui/ # Reusable UI components (Shadcn)
├── lib/
│ ├── config/
│ │ └── settings.ts # Environment configuration with Zod validation
│ ├── modules/
│ │ ├── github/
│ │ │ ├── fetcher.ts # GitHubProfileFetcher (GraphQL)
│ │ │ ├── projects.ts # GitHubProjectRanker
│ │ │ └── contributions.ts # Contribution graph data
│ │ ├── ai/
│ │ │ └── generator.ts # AIDescriptionGenerator (Vercel AI SDK + Groq)
│ │ └── linkedin/
│ │ └── fetcher.ts # LinkedInProfileFetcher
│ └── utils/
│ ├── user.ts # Username validation
│ ├── cache.ts # Database-backed caching with Prisma
│ ├── custom-url.ts # Custom URL validation and management
│ ├── api-client.ts # API client wrapper
│ └── db.ts # Prisma client instance
├── prisma/
│ └── schema.prisma # Database schema (Cache, CustomUrl)
├── types/
│ ├── github.ts # GitHub API types
│ ├── api.ts # API response types
│ └── portfolio.ts # Portfolio data types
└── middleware.ts # API key auth + CORS
Fetches GitHub user profile with AI-generated bio and SEO metadata.
Response:
{
"username": "kartiklabhshetwar",
"name": "Kartik Labhshetwar",
"bio": "software engineer",
"avatar_url": "https://...",
"location": "India",
"email": null,
"website": "https://...",
"twitter_username": "kartik",
"company": "@company",
"followers": 100,
"following": 50,
"public_repos": 42,
"created_at": "2020-01-01T00:00:00Z",
"cached": true,
"about": {
"summary": "...",
"highlights": ["...", "..."],
"skills": ["...", "..."]
},
"seo": {
"title": "...",
"description": "...",
"keywords": ["...", "..."]
}
}Fetches featured GitHub projects with language statistics.
Response:
{
"featured": [
{
"name": "project-name",
"description": "Project description",
"url": "https://github.com/...",
"stars": 42,
"forks": 10,
"language": "TypeScript",
"topics": ["nextjs", "react"],
"updated_at": "2024-01-01T00:00:00Z",
"created_at": "2023-01-01T00:00:00Z",
"languages": {
"TypeScript": 50000,
"JavaScript": 20000
}
}
],
"languages": {
"TypeScript": 150000,
"JavaScript": 80000
},
"total_stars": 200,
"total_repos": 42
}Fetches cached about data for a user.
Response:
{
"about": {
"summary": "...",
"highlights": ["...", "..."],
"skills": ["...", "..."]
}
}Fetches GitHub contribution graph data.
Response:
{
"contributions": [
{
"date": "2024-01-01",
"count": 5
}
]
}Fetches pull requests grouped by organization.
Response:
{
"prsByOrg": [
{
"org": "organization-name",
"count": 10,
"prs": [...]
}
]
}Checks if a custom URL slug is available.
Request:
{
"slug": "john-doe"
}Response:
{
"available": true
}or
{
"available": false,
"error": "This custom URL is already taken. Please choose another one."
}Registers a custom URL for a GitHub username.
Request:
{
"customSlug": "john-doe",
"githubUsername": "johndoe"
}Response:
{
"success": true,
"customSlug": "john-doe"
}Fetches LinkedIn profile data.
Response:
{
"username": "kartiklabhshetwar",
"name": "Kartik Labhshetwar",
"headline": "Full-stack Developer",
"location": "India",
"profile_url": "https://linkedin.com/in/kartik017",
"avatar_url": "https://...",
"summary": "...",
"experience": [],
"education": [],
"skills": []
}Captures screenshots of websites for project previews. This endpoint wraps the Screenshot API service to provide live screenshots of project homepages.
Query Parameters:
url(required): The URL of the website to capturewidth(optional): Viewport width in pixels (default: 1280, max: 3840)height(optional): Viewport height in pixels (default: 800, max: 2160)format(optional): Output format -png,jpeg, orpdf(default:png)quality(optional): Image quality for JPEG format, 1-100 (default: 80)fullPage(optional): Capture full page height (default:false)
Response:
Returns binary image data (PNG/JPEG) or PDF with appropriate Content-Type header.
Example:
GET /api/screenshot?url=https://example.com&width=1280&height=800&format=png
Error Responses:
400: Invalid URL format or parameter validation failed500: Screenshot capture failed503: Screenshot service not configured (SCREENSHOT_API_URLmissing)
Features:
- Automatic fallback to GitHub OpenGraph images if screenshot fails
- HTTP caching headers (24 hours) for performance
- URL validation and parameter sanitization
- Supports multiple output formats
All API endpoints require an X-API-Key header (except when DEBUG=true). The API key must match one of the keys in the API_KEYS environment variable.
- Uses Zod for runtime validation of environment variables
- Type-safe environment variable access
- Validates required keys on application startup
- Provides sensible defaults for optional variables
- GraphQL API for efficient data fetching in a single query
- Fetches user profile, repositories, languages, and contribution data
- Automatic token authentication if
GITHUB_TOKENis provided - Comprehensive error handling for missing users and API errors
- Project ranking algorithm to identify featured repositories
- REST API integration for repository star counts with authentication fallback
- GitHub API version header (
X-GitHub-Api-Version: 2022-11-28) for proper authentication - Automatic retry without token if authentication fails (for public repositories)
- Uses Vercel AI SDK with Groq provider
- Model:
llama-3.1-8b-instant - Generates profile summaries, highlights, and skills
- Generates SEO metadata (title, description, keywords)
- Fallback to extractive summaries on AI errors
- JSON response parsing with robust error handling
- Parallel generation of about and SEO data for performance
- Database-backed caching using PostgreSQL and Prisma
- Cache entries stored with expiration times
- Automatic cleanup of expired entries (1% chance on each write)
- Tag-based cache organization for easy invalidation
- Cache keys use prefix-based naming:
prefix:part1:part2 - Default TTL: 3600 seconds (1 hour)
- Configurable via
CACHE_ENABLEDandDEFAULT_CACHE_TTLenvironment variables - Most portfolio endpoints use caching to reduce API calls
- Cache-aware endpoints check cache before making external API calls
- Validates custom URL slugs with strict rules
- Checks availability in real-time
- Maps custom slugs to GitHub usernames in database
- Reserved word blocking (api, admin, www, etc.)
- Format validation: 3-40 characters, alphanumeric + hyphens only
- Portfolio routing resolves custom URLs to GitHub usernames
- API Key Authentication: Validates
X-API-Keyheader - CORS: Whitelisted origins with fallback for development
- Excluded paths: Static assets, Next.js internals, favicon
- Debug mode: Bypasses auth when
DEBUG=truefor development
- Scores repositories based on:
- Stars (weight: 10)
- Forks (weight: 5)
- Recency (weight: 2)
- Filters out forks and private repositories
- Returns top 12 featured projects
- Aggregates language statistics across all repositories
- Wraps external Screenshot API service for capturing live project screenshots
- Validates URLs and parameters (width, height, format, quality)
- Returns binary image data with proper content types
- Implements HTTP caching (24 hours) for performance
- Graceful error handling with fallback to GitHub OpenGraph images
- Used in project cards to display live previews of project homepages
- Client component (
ProjectImage) handles error states and fallback logic
The application uses PostgreSQL with Prisma ORM. The schema includes:
Stores cached API responses with expiration times:
id: Unique identifierkey: Unique cache keyvalue: Cached data (JSON string)tags: Array of tags for cache organizationexpiresAt: Expiration timestamp- Indexes on
key,expiresAt, andtags
Maps custom URL slugs to GitHub usernames:
id: Unique identifiercustomSlug: Unique custom URL sluggithubUsername: Associated GitHub username- Indexes on
customSlugandgithubUsername
Required:
GROQ_API_KEY: Groq API key for AI generationAPI_KEYS: Comma-separated list of API keys for authenticationDATABASE_URL: PostgreSQL connection string
Optional:
GITHUB_TOKEN: GitHub personal access token (increases rate limits)CACHE_ENABLED: Enable/disable caching (default: true)DEFAULT_CACHE_TTL: Cache time-to-live in seconds (default: 3600)DEBUG: Bypass API key authentication (default: false)NODE_ENV: Environment mode (development/production/test)SCREENSHOT_API_URL: URL of the Screenshot API service (e.g.,https://your-worker.workers.dev) - Required for live project screenshots
- GraphQL over REST: Single query fetches all required GitHub data
- Database-backed caching: Persistent cache across deployments for most endpoints
- Parallel AI generation: About and SEO data generated concurrently
- Automatic cache cleanup: Expired entries removed automatically (1% chance on each write)
- Indexed database queries: Fast lookups for cache and custom URLs
- Next.js App Router: Server components for optimal performance
- Cache-aware endpoints: Check cache before making external API calls to reduce rate limiting
- API Key Authentication: Required for all endpoints (except in DEBUG mode)
- CORS Whitelist: Only allowed origins can access the API
- Input Validation: Username and custom URL format validation
- Error Sanitization: No sensitive data in error messages
- SQL Injection Protection: Prisma ORM handles parameterized queries
- Reserved Word Protection: Prevents custom URLs from conflicting with system routes
When a user visits a portfolio URL, the system:
- Checks if the path matches a custom URL slug in the database
- If found, resolves to the associated GitHub username
- If not found, treats the path as a GitHub username directly
- Fetches and displays the portfolio for the resolved username
This allows both custom URLs (/john-doe) and GitHub usernames (/johndoe) to work seamlessly.
Run linter:
npm run lintBuild for production:
npm run build- Push code to GitHub
- Import project in Vercel
- Set all environment variables in Vercel dashboard
- Deploy
The application will automatically run database migrations on first deployment.
- Set up a PostgreSQL database
- Configure all environment variables
- Run
npm run buildto build the application - Run
npx prisma migrate deployto apply migrations - Start the application with
npm start
- Ensure all required environment variables are set in
.env.local - Check that
GROQ_API_KEY,API_KEYS, andDATABASE_URLare not empty
- Verify
X-API-Keyheader matches one of the keys inAPI_KEYS - Set
DEBUG=trueto bypass authentication during development
- Check username spelling
- Ensure GitHub user exists and is public
- Verify
GITHUB_TOKENif rate limited
- Verify
DATABASE_URLis correct and database is accessible - Ensure migrations have been run with
npx prisma migrate dev
- Verify the custom URL was registered successfully
- Check database for the CustomUrl entry
- Ensure the portfolio route handler is resolving custom URLs correctly
GNU General Public License v3.0 - see LICENSE file for details