Project Zero Documentation

1-Project-Overview
Introduction
The Project Zero is a modern, responsive web application designed to showcase professional work, services, and blog content. Built with React and TypeScript, the site features a clean, interactive design with dark/light theme support, interactive components, and a comprehensive blog system backed by AWS services.
Core-Technologies
Frontend:
- React 18 with TypeScript
- Vite for build and development
- Tailwind CSS for styling
- Framer Motion and GSAP for animations
- Three.js for 3D effects
Backend:
- AWS AppSync for GraphQL API
- DynamoDB for data storage
- Lambda for serverless functions
- SES for email notifications
Infrastructure:
- Terraform for infrastructure as code
- AWS Amplify for hosting and deployment
- GitHub for version control
Key-Features
- 🌓 Dark/Light theme with system preference detection
- 📱 Responsive design for all screen sizes
- 🎨 Interactive card components with drag functionality
- 📝 Contact form with AWS AppSync backend
- 🔒 reCAPTCHA integration for form security
- 📊 Google Analytics integration
- 🎬 GSAP animations and Three.js 3D effects
- 📚 Comprehensive blog system with markdown support
Architecture-Overview
The application follows a component-based architecture with clear separation of concerns:
- Frontend: React components with TypeScript for type safety
- State Management: React Context API for global state
- API Layer: GraphQL with AWS AppSync
- Data Storage: DynamoDB tables for blog posts and contact forms
- Infrastructure: Terraform-managed AWS resources
2-Getting-Started
Prerequisites
- Node.js (v20.17.0 or higher)
- npm (v9.8.1 or higher)
- Git
- AWS Account with configured AWS CLI
- Terraform (v1.0.0 or higher)
Installation
- Clone the repository:
git clone https://github.com/yourusername/project-zero.git cd project-zero
- Install dependencies:
npm install
- Set up environment variables:
cp .env.example .env
- Configure required environment variables:
VITE_APPSYNC_ENDPOINT=your_appsync_endpoint
VITE_APPSYNC_API_KEY=your_api_key
VITE_GA_MEASUREMENT_ID=your_ga_id
Environment-Setup
- Initialize Terraform for infrastructure:
npm run tf:init
- Create a development workspace:
npm run tf:workspace:new -- dev
- Plan and apply infrastructure changes:
npm run tf:plan npm run tf:apply
Development-Workflow
- Start the development server:
npm run dev
- Build for production:
npm run build
- Run tests:
npm run test
- Preview production build:
npm run preview
3-Frontend-Architecture
Project-Structure
src/
├── assets/ # Images, fonts, and other assets
├── components/ # Reusable UI components
│ ├── blog/ # Blog-specific components
│ ├── common/ # Shared UI elements
│ ├── features/ # Feature-specific components
│ ├── home/ # Homepage components
│ ├── layout/ # Layout components
│ ├── navigation/ # Navigation components
│ ├── portfolio/ # Portfolio components
│ ├── shared/ # Shared utility components
│ └── ui/ # Basic UI elements
├── contexts/ # React context providers
├── data/ # Data files and constants
├── graphql/ # GraphQL queries and mutations
├── hooks/ # Custom React hooks
├── lib/ # Helper functions and utilities
├── pages/ # Main application pages
├── scripts/ # Frontend scripts
├── services/ # API services and integrations
├── styles/ # CSS and style definitions
├── types/ # TypeScript type definitions
└── utils/ # Utility functions
Component-Architecture
Core Components:
- Card Component:
interface CardProps { title: string content: string image?: string link?: string state?: 'minimized' | 'normal' | 'expanded' | 'maximized' }
- Layout Components:
interface LayoutProps { children: React.ReactNode className?: string }
- Navigation:
interface NavigationProps { theme: 'light' | 'dark' toggleTheme: () => void }
State-Management
The application uses React Context for global state management:
- Theme Context:
interface ThemeContextType { theme: 'light' | 'dark' toggleTheme: () => void }
- Reset Context:
interface ResetContextType { resetState: () => void isResetting: boolean }
Routing
The application uses React Router with the following main routes:
<Routes> <Route path='/' element={<HomePage />} /> <Route path='/about' element={<AboutPage />} /> <Route path='/projects' element={<Projects />} /> <Route path='/portfolio' element={<Portfolio />} /> <Route path='/services' element={<Services />} /> <Route path='/services/overview' element={<ServiceOverview />} /> <Route path='/services/development' element={<DevelopmentServices />} /> <Route path='/services/design' element={<DesignServices />} /> <Route path='/services/cloud' element={<CloudServices />} /> <Route path='/blog' element={<Blog />} /> <Route path='/blog/:slug' element={<BlogPost />} /> <Route path='/resources' element={<Resources />} /> <Route path='/contact' element={<Contact />} /> </Routes>
Styling-System
The project uses Tailwind CSS with a custom configuration:
// tailwind.config.js module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}'], darkMode: 'class', theme: { extend: { colors: { primary: { 50: 'var(--color-primary-50)', // ... other shades 900: 'var(--color-primary-900)' } // ... other color definitions } // ... other theme extensions } }, plugins: [ require('@tailwindcss/typography'), require('@tailwindcss/forms'), require('@tailwindcss/aspect-ratio') ] }
Animation-Framework
The project uses multiple animation libraries:
- Framer Motion for component animations:
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.5 }} > {children} </motion.div>
- GSAP for complex animations:
useEffect(() => { gsap.to('.element', { duration: 1, x: 100, ease: 'power2.out' }) }, [])
- Three.js for 3D effects:
const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ) const renderer = new THREE.WebGLRenderer() // ... Three.js setup
4-Backend-Services
AWS-AppSync-API
The project uses AWS AppSync for GraphQL API:
API Configuration:
- Authentication: API Key
- Schema: GraphQL schema for contact forms and blog posts
GraphQL Schema:
type ContactForm @model @auth(rules: [{ allow: public }]) { id: ID! name: String! email: String! message: String! createdAt: AWSDateTime! status: String! } type BlogPost @model @auth(rules: [{ allow: public }]) { id: ID! title: String! slug: String! content: String! excerpt: String author: String tags: [String] featuredImage: String status: String! publishedAt: AWSDateTime updatedAt: AWSDateTime }
DynamoDB-Tables
Contact Forms Table:
- Name: ContactForms
- Primary Key: id (String)
- Sort Key: createdAt (String)
- Streams: Enabled for Lambda trigger
Blog Posts Table:
- Name: {project_name}-{environment}-blog-posts
- Primary Key:
- Hash Key: id (String)
- Range Key: slug (String)
- Global Secondary Index:
- Name: StatusIndex
- Hash Key: status (String)
- Range Key: publishedAt (String)
- Projection Type: ALL
Lambda-Functions
Contact Form Processor:
- Runtime: Node.js 18.x
- Trigger: DynamoDB Streams
- Environment Variables:
- CONTACT_FORM_TABLE: ContactForms
- FROM_EMAIL: your-email@example.com
- TO_EMAIL: your-email@example.com
Lambda Function Code:
exports.handler = async (event) => { // Process DynamoDB stream records for (const record of event.Records) { if (record.eventName === 'INSERT') { // Extract contact form data const newItem = record.dynamodb.NewImage // Send email notification await sendEmailNotification(newItem) // Update status in DynamoDB await updateStatus(newItem.id.S, 'PROCESSED') } } return { statusCode: 200 } }
SES-Email-Service
Email Configuration:
- Verified Sender: your-email@example.com
- Verified Recipient: your-email@example.com
- Template: Contact form notification
Email Template:
<h1>New Contact Form Submission</h1> <p><strong>Name:</strong> {{name}}</p> <p><strong>Email:</strong> {{email}}</p> <p><strong>Message:</strong> {{message}}</p> <p><strong>Submitted:</strong> {{createdAt}}</p>
API-Security
Authentication:
- API Key authentication with rate limiting
- Regular key rotation (every 6 months)
- Request validation using GraphQL schema
Authorization:
- Field-level authorization with @auth directives
- IAM roles with least privilege principle
5-Blog-System
Content-Structure
Blog posts are stored in two locations:
- Content Directory:
content/blog/*.md
- Production-ready posts - Source Data:
src/data/blog-posts/*.md
- Development/draft posts
Frontmatter-Metadata
Each blog post requires comprehensive frontmatter metadata:
--- title: Your Blog Post Title slug: url-friendly-post-slug excerpt: A brief summary of the post (140-160 characters recommended) author: Author Name tags: ['Tag1', 'Tag2', 'Tag3'] readingTime: 10 featuredImage: '/images/blog/featured/image-name.jpg' status: published # or 'draft' # SEO metadata description: 'Comprehensive description for search engines (150-160 characters)' keywords: ['keyword1', 'keyword2', 'keyword3'] ogTitle: 'Title for social media sharing' ogDescription: 'Description for social media sharing' ogImage: '/images/blog/featured/image-name.jpg' ogType: 'article' twitterCard: 'summary_large_image' twitterCreator: '@username' articleSection: 'Topic Category' articleAuthor: 'Author Name' datePublished: '2024-03-20' dateModified: '2024-03-20' ---
Image-Management
Directory Structure:
public/
└── images/
└── blog/
├── featured/ # Hero images (1200x630px)
├── thumbnails/ # Thumbnail images (300x300px)
├── optimized/ # In-content images (800px width)
└── original/ # Original unprocessed images
Image Specifications:
-
Featured Images:
- Dimensions: 1200×630 pixels (optimal for social sharing)
- Format: WebP (automatically converted)
- Quality: 85%
- Path:
/images/blog/featured/filename.webp
-
Thumbnail Images:
- Dimensions: 300×300 pixels
- Format: WebP (automatically converted)
- Quality: 80%
- Path:
/images/blog/thumbnails/filename.webp
-
Optimized Content Images:
- Width: 800 pixels (height maintains aspect ratio)
- Format: WebP (automatically converted)
- Quality: 82%
- Path:
/images/blog/optimized/filename.webp
Image Processing:
# Process blog images npm run blog:images # Upload images to S3 npm run blog:s3-upload # Generate static blog JSON npm run blog:json # Import to DynamoDB npm run import:all-blogs
Publishing-Workflow
-
Creating a New Post:
- Create a new markdown file in
content/blog/
- Add required frontmatter metadata
- Write content using markdown formatting
- Add and optimize images
- Set status to
draft
- Create a new markdown file in
-
Testing Locally:
- Run development server:
npm run dev
- Navigate to
/blog/your-post-slug
- Verify content, formatting, and image display
- Test responsiveness across devices
- Run development server:
-
Publishing:
- Update status to
published
- Update
datePublished
anddateModified
- Run
npm run build
to generate production assets - Commit and push changes to repository
- Update status to
GraphQL-API
List Published Posts:
query ListBlogPosts { listBlogPosts(limit: 10) { items { id title slug excerpt publishedAt } nextToken } }
Get Single Post:
query GetBlogPost { getBlogPost(slug: "building-modern-portfolio") { id title content author publishedAt } }
6-Infrastructure-Management
Terraform-Configuration
Main Configuration Files:
terraform/main.tf
: Main infrastructure configurationterraform/variables.tf
: Input variable definitionsterraform/terraform.tfvars
: Environment-specific values
Terraform Variables:
project_name = "project-zero" environment = "dev" aws_region = "us-east-1" from_email = "your-email@example.com" to_email = "your-email@example.com"
AWS-Resources
AppSync API:
resource "aws_appsync_graphql_api" "portfolio_api" { name = "${var.project_name}-${var.environment}-api" authentication_type = "API_KEY" schema = file("${path.module}/schema.graphql") } resource "aws_appsync_api_key" "portfolio_api_key" { api_id = aws_appsync_graphql_api.portfolio_api.id expires = timeadd(timestamp(), "8760h") # 1 year }
DynamoDB Tables:
resource "aws_dynamodb_table" "contact_forms" { name = "ContactForms" billing_mode = "PAY_PER_REQUEST" hash_key = "id" range_key = "createdAt" attribute { name = "id" type = "S" } attribute { name = "createdAt" type = "S" } stream_enabled = true stream_view_type = "NEW_IMAGE" }
Lambda Function:
resource "aws_lambda_function" "process_form" { function_name = "${var.project_name}-${var.environment}-process-form" handler = "index.handler" runtime = "nodejs18.x" role = aws_iam_role.lambda_role.arn filename = "${path.module}/lambda/process-form.zip" source_code_hash = filebase64sha256("${path.module}/lambda/process-form.zip") environment { variables = { CONTACT_FORM_TABLE = aws_dynamodb_table.contact_forms.name FROM_EMAIL = var.from_email TO_EMAIL = var.to_email } } }
IAM-Roles-and-Policies
Lambda Role:
resource "aws_iam_role" "lambda_role" { name = "${var.project_name}-${var.environment}-lambda-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } } ] }) } resource "aws_iam_role_policy" "lambda_policy" { name = "${var.project_name}-${var.environment}-lambda-policy" role = aws_iam_role.lambda_role.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = [ "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:Query", "dynamodb:Scan" ] Effect = "Allow" Resource = aws_dynamodb_table.contact_forms.arn }, { Action = [ "dynamodb:GetRecords", "dynamodb:GetShardIterator", "dynamodb:DescribeStream", "dynamodb:ListStreams" ] Effect = "Allow" Resource = "${aws_dynamodb_table.contact_forms.arn}/stream/*" }, { Action = [ "ses:SendEmail", "ses:SendRawEmail" ] Effect = "Allow" Resource = "*" }, { Action = [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ] Effect = "Allow" Resource = "arn:aws:logs:*:*:*" } ] }) }
Infrastructure-Workflows
Available Commands:
# Initialize Terraform working directory npm run tf:init # Create an execution plan npm run tf:plan # Apply changes (with confirmation prompt) npm run tf:apply # Apply changes automatically without confirmation npm run tf:apply:auto # Validate Terraform configuration files npm run tf:validate # Destroy infrastructure (with confirmation prompt) npm run tf:destroy # Destroy infrastructure automatically without confirmation npm run tf:destroy:auto
Environment-Management
Workspace Management:
# Create a new workspace npm run tf:workspace:new -- workspace_name # Select an existing workspace npm run tf:workspace:select -- workspace_name # List all workspaces npm run tf:workspace:list
Environment Structure:
dev
- Development environmentstaging
- Testing and QAprod
- Production environment
7-Deployment
Build-Process
Build Configuration:
// vite.config.ts export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], ui: ['framer-motion'] } } } } })
Build Commands:
# Build for production npm run build # Preview production build npm run preview
AWS-Amplify-Setup
IAM Role Configuration:
- Run
npm run tf:apply
to create the AWS resources including the Amplify build role - Note the output value for
amplify_build_role_arn
- Go to the AWS Amplify Console
- Select your application
- Navigate to "General" settings
- Under "Service role", click "Edit"
- Select "Existing service role"
- Enter the ARN from step 2 (
amplify_build_role_arn
) - Click "Save"
Environment Variables: Configure the following environment variables in the AWS Amplify Console:
SKIP_PREBUILD
: Set to 'true' to skip image processing during buildsNODE_ENV
: Set to 'production' for production builds
Environment-Variables
Required Environment Variables:
VITE_APPSYNC_ENDPOINT=https://your-api-id.appsync-api.region.amazonaws.com/graphql
VITE_APPSYNC_API_KEY=your-api-key
VITE_GA_MEASUREMENT_ID=your-ga-id
Optional Environment Variables:
VITE_SES_FROM_EMAIL=your-verified@email.com
VITE_SES_TO_EMAIL=recipient@email.com
Deployment-Pipeline
GitHub Actions Integration: The blog content processing workflow requires AWS credentials:
- Go to your GitHub repository settings
- Navigate to "Secrets and variables" > "Actions"
- Add the following repository secrets:
AWS_ACCESS_KEY_ID
: Your AWS access key with S3 and DynamoDB permissionsAWS_SECRET_ACCESS_KEY
: Your AWS secret key
Deployment Workflow:
name: Deploy on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '20' - name: Install dependencies run: npm ci - name: Process blog content run: npm run blog:json env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - name: Build run: npm run build # Amplify automatically deploys on push to main
8-Performance-Optimization
Code-Splitting
Route-Based Code Splitting:
// Lazy load components const About = React.lazy(() => import('./pages/About')) const Projects = React.lazy(() => import('./pages/Projects')) const Blog = React.lazy(() => import('./pages/Blog')) // In router ;<Routes> <Route path='/' element={<HomePage />} /> <Route path='/about' element={ <Suspense fallback={<Loading />}> <About /> </Suspense> } /> {/* Other routes */} </Routes>
Component-Level Code Splitting:
// Lazy load heavy components const ThreeJSScene = React.lazy(() => import('./components/ThreeJSScene')) // In component { showScene && ( <Suspense fallback={<Loading />}> <ThreeJSScene /> </Suspense> ) }
Image-Optimization
Responsive Images:
<img src={`/images/blog/optimized/${imageName}-800.webp`} srcSet={` /images/blog/optimized/${imageName}-400.webp 400w, /images/blog/optimized/${imageName}-800.webp 800w, /images/blog/optimized/${imageName}-1200.webp 1200w `} sizes='(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px' alt={altText} loading='lazy' />
Image Processing:
// scripts/blog-image-processor.ts import sharp from 'sharp' async function processImage(inputPath, outputPath, width, height, quality) { await sharp(inputPath) .resize(width, height) .webp({ quality }) .toFile(outputPath) } // Process featured image await processImage( `src/assets/${filename}`, `public/images/blog/featured/${basename}.webp`, 1200, 630, 85 )
Bundle-Size-Management
Dependency Analysis:
npm run analyze
Chunk Optimization:
// vite.config.ts export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], ui: ['framer-motion'], three: ['three'], gsap: ['gsap'] } } } } })
Caching-Strategy
Static Asset Caching:
// vite.config.ts export default defineConfig({ build: { rollupOptions: { output: { assetFileNames: 'assets/[name].[hash].[ext]' } } } })
API Response Caching:
// services/api.ts const cachedData = new Map() export async function fetchBlogPost(slug) { const cacheKey = `blog-${slug}` if (cachedData.has(cacheKey)) { return cachedData.get(cacheKey) } const data = await fetchFromAPI(/* GraphQL query */) cachedData.set(cacheKey, data) return data }
9-Testing
Example Unit Test:
// utils/formatDate.test.ts import { describe, it, expect } from 'vitest' import { formatDate } from './formatDate' describe('formatDate', () => { it('formats date correctly', () => { const date = new Date('2024-03-20T12:00:00Z') expect(formatDate(date)).toBe('March 20, 2024') }) it('handles invalid dates', () => { expect(formatDate(null)).toBe('') expect(formatDate(undefined)).toBe('') expect(formatDate(new Date('invalid'))).toBe('Invalid Date') }) })
Component Testing
Testing Library Setup:
// src/test/setup.ts import '@testing-library/jest-dom' import { vi } from 'vitest' // Mock IntersectionObserver global.IntersectionObserver = vi.fn().mockImplementation(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() })) // Mock matchMedia global.matchMedia = vi.fn().mockImplementation((query) => ({ matches: false, media: query, onchange: null, addListener: vi.fn(), removeListener: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn() }))
Component Test Example:
// components/Card.test.tsx import { render, screen, fireEvent } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' import Card from './Card' describe('Card Component', () => { it('renders with title and content', () => { render(<Card title='Test Card' content='Test Content' />) expect(screen.getByText('Test Card')).toBeInTheDocument() expect(screen.getByText('Test Content')).toBeInTheDocument() }) it('handles click events', () => { const handleClick = vi.fn() render( <Card title='Clickable Card' content='Click me' onClick={handleClick} /> ) fireEvent.click(screen.getByText('Clickable Card')) expect(handleClick).toHaveBeenCalledTimes(1) }) it('renders in different states', () => { const { rerender } = render( <Card title='State Card' content='Content' state='normal' /> ) expect(screen.getByText('State Card')).toHaveClass('card-normal') rerender(<Card title='State Card' content='Content' state='expanded' />) expect(screen.getByText('State Card')).toHaveClass('card-expanded') }) })
Integration Testing
API Integration Test:
// services/api.integration.test.ts import { describe, it, expect, beforeAll, afterAll } from 'vitest' import { fetchBlogPosts, fetchBlogPost } from './api' import { setupServer } from 'msw/node' import { rest, graphql } from 'msw' const server = setupServer( graphql.query('ListBlogPosts', (req, res, ctx) => { return res( ctx.data({ listBlogPosts: { items: [ { id: '1', title: 'Test Post', slug: 'test-post', excerpt: 'Test excerpt', publishedAt: '2024-03-20T12:00:00Z' } ], nextToken: null } }) ) }), graphql.query('GetBlogPost', (req, res, ctx) => { const { slug } = req.variables if (slug === 'test-post') { return res( ctx.data({ getBlogPost: { id: '1', title: 'Test Post', slug: 'test-post', content: 'Test content', author: 'Test Author', publishedAt: '2024-03-20T12:00:00Z' } }) ) } return res(ctx.errors([{ message: 'Post not found' }])) }) ) beforeAll(() => server.listen()) afterAll(() => server.close()) describe('API Integration', () => { it('fetches blog posts', async () => { const posts = await fetchBlogPosts() expect(posts).toHaveLength(1) expect(posts[0].title).toBe('Test Post') }) it('fetches a single blog post', async () => { const post = await fetchBlogPost('test-post') expect(post.title).toBe('Test Post') expect(post.content).toBe('Test content') }) it('handles errors when post not found', async () => { await expect(fetchBlogPost('non-existent')).rejects.toThrow( 'Post not found' ) }) })
Test Coverage
Coverage Configuration:
// vitest.config.ts export default defineConfig({ // ... other config test: { // ... other test config coverage: { provider: 'c8', reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/', 'src/test/', '**/*.d.ts', '**/*.test.{ts,tsx}', '**/types/**' ] } } })
Coverage Commands:
# Run tests with coverage npm run test:coverage # View coverage report open coverage/index.html
10-Security-Considerations
Frontend Security
Input Validation:
// components/ContactForm.tsx const validateForm = (data: ContactFormData): FormErrors => { const errors: FormErrors = {} if (!data.name.trim()) { errors.name = 'Name is required' } if (!data.email.trim()) { errors.email = 'Email is required' } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) { errors.email = 'Invalid email format' } if (!data.message.trim()) { errors.message = 'Message is required' } else if (data.message.length < 10) { errors.message = 'Message must be at least 10 characters' } return errors }
XSS Prevention:
// utils/sanitize.ts import DOMPurify from 'dompurify' export const sanitizeHtml = (html: string): string => { return DOMPurify.sanitize(html, { ALLOWED_TAGS: [ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'a', 'strong', 'em', 'code', 'pre', 'blockquote' ], ALLOWED_ATTR: ['href', 'target', 'rel'] }) }
reCAPTCHA Integration:
// components/ContactForm.tsx import ReCAPTCHA from 'react-google-recaptcha' const ContactForm: React.FC = () => { const [recaptchaToken, setRecaptchaToken] = useState<string | null>(null) const handleRecaptchaChange = (token: string | null) => { setRecaptchaToken(token) } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!recaptchaToken) { setError('Please complete the reCAPTCHA verification') return } // Form submission logic } return ( <form onSubmit={handleSubmit}> {/* Form fields */} <ReCAPTCHA sitekey={import.meta.env.VITE_RECAPTCHA_SITE_KEY} onChange={handleRecaptchaChange} /> <button type='submit' disabled={!recaptchaToken}> Submit </button> </form> ) }
API Security
Rate Limiting:
// services/api.ts import { createRateLimiter } from './rateLimiter' const apiLimiter = createRateLimiter({ maxRequests: 10, timeWindow: 60000 // 1 minute }) export const fetchWithRateLimit = async ( url: string, options?: RequestInit ) => { await apiLimiter.acquire() try { const response = await fetch(url, options) return response } catch (error) { throw error } }
API Key Rotation:
// terraform/main.tf resource "aws_appsync_api_key" "portfolio_api_key" { api_id = aws_appsync_graphql_api.portfolio_api.id expires = timeadd(timestamp(), "4380h") # 6 months }
Error Handling:
// services/api.ts export const handleApiError = (error: any): never => { // Log error details for debugging console.error('API Error:', error) // Return user-friendly error message if (error.networkError) { throw new Error( 'Network error. Please check your connection and try again.' ) } if (error.graphQLErrors) { const errorMessage = error.graphQLErrors .map((e: any) => e.message) .join('. ') throw new Error(`Request failed: ${errorMessage}`) } throw new Error('An unexpected error occurred. Please try again later.') }
Infrastructure Security
IAM Least Privilege:
# terraform/main.tf resource "aws_iam_role_policy" "lambda_policy" { name = "${var.project_name}-${var.environment}-lambda-policy" role = aws_iam_role.lambda_role.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = [ "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem" ] Effect = "Allow" Resource = aws_dynamodb_table.contact_forms.arn }, { Action = [ "ses:SendEmail" ] Effect = "Allow" Resource = "arn:aws:ses:${var.aws_region}:${data.aws_caller_identity.current.account_id}:identity/${var.from_email}" } ] }) }
Secure Headers:
// vite.config.ts export default defineConfig({ // ... other config plugins: [ // ... other plugins { name: 'security-headers', configureServer(server) { server.middlewares.use((req, res, next) => { res.setHeader('X-Content-Type-Options', 'nosniff') res.setHeader('X-Frame-Options', 'DENY') res.setHeader('X-XSS-Protection', '1; mode=block') res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin') res.setHeader( 'Permissions-Policy', 'camera=(), microphone=(), geolocation=()' ) next() }) } } ] })
Data Protection
Sensitive Data Handling:
// services/contact.ts export const submitContactForm = async (formData: ContactFormData) => { // Remove any sensitive data before logging const loggableData = { ...formData, email: `${formData.email.substring(0, 3)}...${ formData.email.split('@')[1] }`, message: `${formData.message.substring(0, 10)}...` } console.log('Submitting contact form:', loggableData) // Submit form data const response = await API.graphql({ query: createContactFormMutation, variables: { input: formData } }) return response }
Environment Variable Protection:
// vite.config.ts export default defineConfig({ // ... other config define: { // Only expose specific environment variables to the client 'import.meta.env.VITE_APPSYNC_ENDPOINT': JSON.stringify( process.env.VITE_APPSYNC_ENDPOINT ), 'import.meta.env.VITE_GA_MEASUREMENT_ID': JSON.stringify( process.env.VITE_GA_MEASUREMENT_ID ), 'import.meta.env.VITE_RECAPTCHA_SITE_KEY': JSON.stringify( process.env.VITE_RECAPTCHA_SITE_KEY ) } })
11-Monitoring-and-Analytics
Performance Monitoring
Core Web Vitals:
// utils/webVitals.ts import { onCLS, onFID, onLCP, onTTFB } from 'web-vitals' export const reportWebVitals = (onPerfEntry?: (metric: any) => void) => { if (onPerfEntry && onPerfEntry instanceof Function) { onCLS(onPerfEntry) onFID(onPerfEntry) onLCP(onPerfEntry) onTTFB(onPerfEntry) } } // In main.tsx reportWebVitals((metric) => { console.log(metric) // Send to analytics if (window.gtag) { window.gtag('event', 'web_vitals', { event_category: 'Web Vitals', event_label: metric.name, value: Math.round(metric.value), non_interaction: true }) } })
Performance Marks:
// components/BlogPost.tsx useEffect(() => { // Mark start of content loading performance.mark('blog-content-start') fetchBlogPost(slug) .then((data) => { setPost(data) // Mark end of content loading performance.mark('blog-content-end') performance.measure( 'blog-content-load', 'blog-content-start', 'blog-content-end' ) const measure = performance.getEntriesByName('blog-content-load')[0] console.log(`Blog content loaded in ${measure.duration}ms`) }) .catch(setError) }, [slug])
Error Tracking
Global Error Boundary:
// components/ErrorBoundary.tsx import React, { Component, ErrorInfo, ReactNode } from 'react' interface Props { children: ReactNode fallback?: ReactNode } interface State { hasError: boolean error?: Error } class ErrorBoundary extends Component<Props, State> { constructor(props: Props) { super(props) this.state = { hasError: false } } static getDerivedStateFromError(error: Error): State { return { hasError: true, error } } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { console.error('Error caught by boundary:', error, errorInfo) // Report to monitoring service if (window.gtag) { window.gtag('event', 'exception', { description: `${error.name}: ${error.message}`, fatal: false }) } } render(): ReactNode { if (this.state.hasError) { return ( this.props.fallback || ( <div className='error-container'> <h2>Something went wrong</h2> <p>We're sorry, but an error occurred while rendering this page.</p> <button onClick={() => this.setState({ hasError: false })}> Try again </button> </div> ) ) } return this.props.children } } export default ErrorBoundary
API Error Logging:
// services/api.ts export const logApiError = (error: any, context: string) => { // Log to console console.error(`API Error (${context}):`, error) // Send to monitoring service if (window.gtag) { window.gtag('event', 'api_error', { event_category: 'API', event_label: context, description: error.message || 'Unknown error' }) } // You could also send to a dedicated error tracking service }
User Analytics
Google Analytics Integration:
// components/GoogleAnalytics.tsx import { useEffect } from 'react' import { useLocation } from 'react-router-dom' declare global { interface Window { gtag: ( command: string, action: string, params?: Record<string, any> ) => void } } const GoogleAnalytics: React.FC = () => { const location = useLocation() const measurementId = import.meta.env.VITE_GA_MEASUREMENT_ID useEffect(() => { if (!measurementId) return // Initialize Google Analytics const script = document.createElement('script') script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}` script.async = true document.head.appendChild(script) window.dataLayer = window.dataLayer || [] window.gtag = function gtag() { window.dataLayer.push(arguments) } window.gtag('js', new Date()) window.gtag('config', measurementId, { send_page_view: false }) return () => { document.head.removeChild(script) } }, [measurementId]) useEffect(() => { if (!measurementId || !window.gtag) return // Track page views window.gtag('event', 'page_view', { page_title: document.title, page_location: window.location.href, page_path: location.pathname }) }, [location, measurementId]) return null } export default GoogleAnalytics
Custom Event Tracking:
// hooks/useAnalytics.ts import { useCallback } from 'react' interface EventParams { action: string category?: string label?: string value?: number [key: string]: any } export const useAnalytics = () => { const trackEvent = useCallback((params: EventParams) => { if (!window.gtag) return window.gtag('event', params.action, { event_category: params.category || 'general', event_label: params.label, value: params.value, ...Object.fromEntries( Object.entries(params).filter( ([key]) => !['action', 'category', 'label', 'value'].includes(key) ) ) }) }, []) return { trackEvent } }
AWS CloudWatch
Lambda Logging:
// lambda/process-form/index.js exports.handler = async (event) => { console.log('Event received:', JSON.stringify(event)) try { // Process records for (const record of event.Records) { console.log('Processing record:', record.eventID) // Extract data const newItem = record.dynamodb.NewImage console.log('New item:', JSON.stringify(newItem)) // Process form await processContactForm(newItem) } console.log('Processing completed successfully') return { statusCode: 200 } } catch (error) { console.error('Error processing records:', error) throw error } }
CloudWatch Alarms:
# terraform/monitoring.tf resource "aws_cloudwatch_metric_alarm" "lambda_errors" { alarm_name = "${var.project_name}-${var.environment}-lambda-errors" comparison_operator = "GreaterThanThreshold" evaluation_periods = "1" metric_name = "Errors" namespace = "AWS/Lambda" period = "60" statistic = "Sum" threshold = "0" alarm_description = "This metric monitors lambda function errors" dimensions = { FunctionName = aws_lambda_function.process_form.function_name } alarm_actions = [aws_sns_topic.alerts.arn] } resource "aws_sns_topic" "alerts" { name = "${var.project_name}-${var.environment}-alerts" }
12-Maintenance
Regular Updates
Dependency Updates:
# Check for outdated dependencies npm outdated # Update dependencies npm update # Update a specific package npm update react react-dom # Update to latest major versions (with caution) npx npm-check-updates -u npm install
Security Updates:
# Check for security vulnerabilities npm audit # Fix security issues npm audit fix # Fix security issues with major version changes npm audit fix --force
Backup Strategy
Code Repository:
- Main repository on GitHub
- Regular commits and pushes
- Protected main branch
- Pull request workflow
DynamoDB Backups:
# terraform/backup.tf resource "aws_dynamodb_table" "blog_posts" { # ... other configuration point_in_time_recovery { enabled = true } }
Content Backups:
# Export blog posts to JSON npm run blog:export # Backup images to S3 npm run blog:backup-images
Security Audits
Regular Security Checks:
# Check for security vulnerabilities npm audit # Check for outdated dependencies npm outdated # Run security linting npm run lint:security
AWS Security Checks:
# Check AWS security with AWS CLI aws securityhub get-findings --filter '{"ResourceAwsAccountId": [{"Value": "YOUR_ACCOUNT_ID", "Comparison": "EQUALS"}], "WorkflowStatus": [{"Value": "NEW", "Comparison": "EQUALS"}]}' # Check IAM access analyzer aws accessanalyzer list-findings
Content Management
Blog Post Updates:
# Update blog posts in DynamoDB npm run blog:update # Rebuild and deploy npm run build
Image Management:
# Process new blog images npm run blog:images # Upload images to S3 npm run blog:s3-upload
13-Troubleshooting
Common Issues
Build Failures:
Issue: Build fails with "Cannot find module"
Solution:
1. Clear npm cache: npm cache clean --force
2. Delete node_modules: rm -rf node_modules
3. Reinstall dependencies: npm install
4. Try building again: npm run build
API Connection Issues:
Issue: Cannot connect to AppSync API
Solution:
1. Check environment variables are set correctly
2. Verify API key is valid and not expired
3. Check network connectivity
4. Verify CORS settings in AppSync console
Image Loading Issues:
Issue: Blog images not loading
Solution:
1. Verify image paths are correct
2. Check if images exist in the correct directory
3. Run image processing: npm run blog:images
4. Check browser console for 404 errors
5. Verify S3 permissions if using S3 for images
Debugging Techniques
React DevTools:
1. Install React DevTools browser extension
2. Use Components tab to inspect component hierarchy
3. Use Profiler tab to identify performance bottlenecks
Network Debugging:
1. Open browser DevTools
2. Go to Network tab
3. Filter by XHR/Fetch requests
4. Look for failed requests (red)
5. Check request/response details
Performance Debugging:
1. Open browser DevTools
2. Go to Performance tab
3. Record page load or interaction
4. Analyze render times and bottlenecks
5. Look for long tasks and layout shifts
AWS Troubleshooting
Lambda Function Logs:
# Get Lambda function logs aws logs get-log-events \ --log-group-name "/aws/lambda/jeff-dev-process-form" \ --log-stream-name "$(aws logs describe-log-streams \ --log-group-name "/aws/lambda/jeff-dev-process-form" \ --order-by LastEventTime \ --descending \ --limit 1 \ --query 'logStreams[0].logStreamName' \ --output text)"
AppSync Testing:
1. Go to AWS AppSync Console
2. Select your API
3. Go to Queries tab
4. Test queries and mutations directly
5. Check response and errors
DynamoDB Inspection:
# Scan DynamoDB table aws dynamodb scan \ --table-name ContactForms \ --limit 10
Support Resources
Documentation:
- React Documentation: https://reactjs.org/docs
- TypeScript Documentation: https://www.typescriptlang.org/docs
- AWS AppSync Documentation: https://docs.aws.amazon.com/appsync
- Terraform Documentation: https://www.terraform.io/docs
Community Support:
- Stack Overflow
- GitHub Issues
- AWS Forums
- React Discord
14-Best-Practices
Code Organization
File Structure:
- Group by feature, not by type
- Keep related files close together
- Use index files for cleaner imports
- Maintain consistent naming conventions
Import Organization:
// External dependencies import React, { useState, useEffect } from 'react' import { motion } from 'framer-motion' // Internal utilities and hooks import { formatDate } from '@/utils/formatDate' import { useTheme } from '@/hooks/useTheme' // Components import Card from '@/components/common/Card' import Button from '@/components/ui/Button' // Styles and assets import '@/styles/BlogPost.css' import blogImage from '@/assets/blog.jpg'
Component Design
Component Principles:
- Single Responsibility: Each component should do one thing well
- Composition: Prefer composition over inheritance
- Reusability: Design components for reuse
- Testability: Make components easy to test
Component Template:
import React from 'react' interface ComponentProps { // Define props with TypeScript title: string onClick?: () => void } /** * Component description * * @example * <Component title="Example" onClick={() => console.log('clicked')} /> */ const Component: React.FC<ComponentProps> = ({ title, onClick }) => { // Component logic return ( <div className='component' onClick={onClick}> <h2>{title}</h2> </div> ) } export default Component
State Management
State Management Principles:
- Keep state as local as possible
- Lift state up when needed
- Use context for global state
- Consider performance implications
Custom Hooks:
// hooks/useLocalStorage.ts import { useState, useEffect } from 'react' export function useLocalStorage<T>( key: string, initialValue: T ): [T, (value: T) => void] { // Get from local storage then // parse stored json or return initialValue const readValue = (): T => { if (typeof window === 'undefined') { return initialValue } try { const item = window.localStorage.getItem(key) return item ? JSON.parse(item) : initialValue } catch (error) { console.warn(`Error reading localStorage key "${key}":`, error) return initialValue } } // State to store our value const [storedValue, setStoredValue] = useState<T>(readValue) // Return a wrapped version of useState's setter function that // persists the new value to localStorage const setValue = (value: T): void => { try { // Allow value to be a function so we have same API as useState const valueToStore = value instanceof Function ? value(storedValue) : value // Save state setStoredValue(valueToStore) // Save to local storage if (typeof window !== 'undefined') { window.localStorage.setItem(key, JSON.stringify(valueToStore)) } } catch (error) { console.warn(`Error setting localStorage key "${key}":`, error) } } useEffect(() => { setStoredValue(readValue()) }, []) return [storedValue, setValue] }
Performance Optimization
Memoization:
import React, { useMemo, useCallback } from 'react' interface ExpensiveComponentProps { data: number[] onItemClick: (item: number) => void } const ExpensiveComponent: React.FC<ExpensiveComponentProps> = ({ data, onItemClick }) => { // Memoize expensive calculations const processedData = useMemo(() => { console.log('Processing data...') return data.map((item) => item * 2) }, [data]) // Memoize callback functions const handleItemClick = useCallback( (item: number) => { console.log('Item clicked:', item) onItemClick(item) }, [onItemClick] ) return ( <div> {processedData.map((item, index) => ( <button key={index} onClick={() => handleItemClick(item)}> {item} </button> ))} </div> ) } // Memoize the entire component export default React.memo(ExpensiveComponent)
Virtualization:
import React from 'react' import { FixedSizeList as List } from 'react-window' interface ItemData { items: string[] onItemClick: (item: string) => void } const Row: React.FC<{ index: number style: React.CSSProperties data: ItemData }> = ({ index, style, data }) => { const { items, onItemClick } = data const item = items[index] return ( <div style={style} onClick={() => onItemClick(item)} className='list-item'> {item} </div> ) } const VirtualizedList: React.FC<{ items: string[] onItemClick: (item: string) => void }> = ({ items, onItemClick }) => { const itemData: ItemData = { items, onItemClick } return ( <List height={400} width='100%' itemCount={items.length} itemSize={50} itemData={itemData} > {Row} </List> ) } export default VirtualizedList
Security
Content Security Policy:
<!-- public/index.html --> <meta http-equiv="Content-Security-Policy" content=" default-src 'self'; script-src 'self' https://www.googletagmanager.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https://example.cloudfront.net; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://*.amazonaws.com https://*.appsync-api.*.amazonaws.com; frame-src https://www.google.com/recaptcha/; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests; " />
Secure Coding Practices:
- Validate all inputs
- Sanitize outputs
- Use HTTPS for all requests
- Implement proper authentication and authorization
- Keep dependencies updated
- Follow the principle of least privilege
- Implement proper error handling
- Use secure headers
- Protect against common web vulnerabilities (XSS, CSRF, etc.)
- Regularly audit code for security issues
15-Resources
Documentation
Official Documentation:
- React Documentation
- TypeScript Documentation
- Vite Documentation
- Tailwind CSS Documentation
- AWS AppSync Documentation
- AWS Amplify Documentation
- Terraform Documentation
Additional Resources:
Tools
Development Tools:
- VS Code - Code editor
- React DevTools - React debugging
- AWS CLI - AWS command line interface
- Terraform CLI - Infrastructure as code
- Postman - API testing
VS Code Extensions:
- ESLint
- Prettier
- Tailwind CSS IntelliSense
- GraphQL
- AWS Toolkit
- Terraform
- vscode-styled-components
Libraries
Core Libraries:
- React - UI library
- TypeScript - Type system
- React Router - Routing
- Tailwind CSS - Utility-first CSS
- Framer Motion - Animation library
- GSAP - Animation library
- Three.js - 3D library
- AWS Amplify - Cloud development platform
Testing Libraries:
- Vitest - Testing framework
- Testing Library - Testing utilities
- MSW - API mocking
Utility Libraries:
- date-fns - Date utilities
- lodash - General utilities
- zod - Schema validation
- DOMPurify - HTML sanitization
References
Style Guides:
Performance:
Security: