Project: FlowMersion Winter Sports Application Marketing Site
Client: Baseline Tech
Tech Stack: React, TypeScript, Tailwind CSS, Next.js
Last Updated: November 2025
- Project Overview
- Getting Started
- Tech Stack & Why We Chose It
- Project Structure
- Design System
- How to Customize
- Component Guide
- Responsive Design
- Deployment
- Troubleshooting
SnowIn is a modern, responsive marketing website for a winter sports tracking application. The site showcases the app's features, builds trust through partner logos and testimonials, and encourages community engagement.
Key Features:
- Fully responsive design (mobile, tablet, desktop)
- Modern glassmorphism effects
- Smooth animations and interactions
- Optimized performance
- Accessible and SEO-friendly
- Node.js 18+ installed
- npm or yarn package manager
# Clone the repository
git clone [repository-url]
# Navigate to project directory
cd baseline-tech-website
# Navigate to frontend folder
cd frontend
# Install dependencies
npm install
# Run development server
npm run dev
# Open browser to http://localhost:3000npm run dev # Start development server
npm run build # Build for production
npm run start # Start production server
npm run lint # Run code lintingWhy: Industry-standard UI library with excellent performance and developer experience.
- Component reusability
- Strong ecosystem
- Easy to maintain and scale
Why: Type safety prevents bugs and improves code quality.
- Catches errors during development (before production)
- Better IDE support and autocomplete
- Self-documenting code
Why: Utility-first CSS framework for rapid, consistent styling.
- No CSS file switching - style directly in components
- Built-in responsive design utilities
- Consistent spacing and sizing scale
- Smaller bundle size (only includes used classes)
- Easy to customize colors and spacing
Why: React framework with built-in optimizations.
- Image optimization (faster loading)
- SEO-friendly
- Simple routing
- Production-ready out of the box
baseline-tech-website/
├── frontend/ # Main Next.js application
│ ├── .next/ # Next.js build output
│ ├── app/ # Next.js app directory
│ │ ├── favicon.ico # Site favicon
│ │ ├── globals.css # Global styles
│ │ ├── homepage.css # Homepage-specific styles
│ │ ├── layout.tsx # Root layout
│ │ ├── page.module.css # Page-specific module styles
│ │ └── page.tsx # Homepage component
│ ├── components/ # Reusable React components
│ │ ├── Carousel.jsx # Image carousel component
│ │ ├── Footer.tsx # Footer component
│ │ └── NavBar.tsx # Navigation bar component
│ ├── node_modules/ # Dependencies (auto-generated)
│ ├── public/ # Static assets (images, fonts, etc.)
│ ├── .gitignore # Git ignore rules for frontend
│ ├── eslint.config.mjs # ESLint configuration
│ ├── next-env.d.ts # Next.js TypeScript declarations
│ ├── next.config.ts # Next.js configuration
│ ├── package-lock.json # Locked dependency versions
│ ├── package.json # Frontend dependencies & scripts
│ ├── postcss.config.js # PostCSS configuration
│ ├── README.md # Frontend documentation
│ ├── tailwind.config.js # Tailwind CSS configuration
│ └── tsconfig.json # TypeScript configuration
├── backend/ # Backend services
│ ├── .gitignore # Git ignore rules for backend
│ ├── package-lock.json # Locked dependency versions
│ ├── package.json # Backend dependencies & scripts
│ └── server.js # Backend server entry point
└── README.md # Main project documentation (this file)
Key Directories:
frontend/app/ - Contains the main application pages and layouts using Next.js App Router
frontend/components/ - Reusable React components like NavBar, Footer, and Carousel
frontend/public/ - Static assets like images, icons, and fonts (accessible at / in the browser)
backend/ - Backend API server (if applicable)
Note: All development work happens in the frontend/ folder. Navigate there before running any npm commands.
Our color scheme is designed for winter sports aesthetics with high contrast for readability.
// tailwind.config.js
colors: {
// Primary Brand Colors
'snow-blue': '#65B4D0', // Main brand blue
'snow-blue-dark': '#4A90B0', // Hover states
// Neutral Grays
'gray-50': '#F9FAFB', // Light backgrounds
'gray-900': '#111827', // Main background
'gray-800': '#1F2937', // Card backgrounds
'gray-700': '#374151', // Borders
'gray-600': '#4B5563', // Secondary text
'gray-300': '#D1D5DB', // Light text
// Accent Colors
'ice-blue': '#E0F2FE', // Highlights
'mountain-gray': '#6B7280', // Decorative elements
}// Font Families
fontFamily: {
'manrope': ['Manrope', 'sans-serif'], // Headings
'sans': ['Inter', 'sans-serif'], // Body text
}
// Font Sizes (Responsive)
Mobile: text-4xl (36px) → Headings
text-base (16px) → Body
text-sm (14px) → Small text
text-xs (12px) → Labels
Desktop: text-6xl (60px) → Headings
text-lg (18px) → Body
text-base (16px) → Small text
text-sm (14px) → LabelsWe use a consistent 8px-based spacing scale:
gap-4 = 1rem (16px)
gap-8 = 2rem (32px)
gap-12 = 3rem (48px)
gap-16 = 4rem (64px)
gap-20 = 5rem (80px)
Why 8px base? It scales perfectly across all screen sizes and aligns with common design grids.
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
// Change the main brand blue
'snow-blue': '#YOUR_COLOR_HERE', // Replace with your hex code
// Change background
'gray-900': '#YOUR_DARK_COLOR',
'gray-800': '#YOUR_CARD_COLOR',
}
}
}
}After changing, restart your dev server:
npm run devFind the specific component and change the className:
// Before
// After - change to blue background
// Or use custom color directlyAll text is in the component files. Example:
// components/HeroSection.tsx
Meet Snowin // Change this text
Your Potential, Our Passion // Change this text- Add your image to
/public/images/ - Update the import in the component:
// Before
import heroImage from '@/public/images/hero/snowboarder.jpg'
// After - use your image
import heroImage from '@/public/images/hero/YOUR_IMAGE.jpg'// Current spacing
// Vertical: 5rem, Horizontal: 2rem
// More spacing
// Vertical: 8rem, Horizontal: 3rem
// Less spacing
// Vertical: 3rem, Horizontal: 1remLocation: components/HeroSection.tsx
Purpose: First impression, main headline
Customizable:
- Headline text
- Subheading text
- Background gradient colors
- Snowboard image
// Key classes to modify
className="bg-gray-900" // Background color
className="text-4xl lg:text-6xl" // Heading size
className="text-snow-blue" // Text colorLocation: components/PotentialSection.tsx
Purpose: Value proposition with sensor image
Customizable:
- Headline
- Body copy
- Button text and link
- Product image
- Background color
Location: components/ProductsSection.tsx
Purpose: Showcase partner brands/products
Customizable:
- Number of product cards (add/remove)
- Product images
- Product names
- Card hover effects
Location: components/ImproveSection.tsx
Purpose: Show app features with phone mockup
Research Decision: We used a 2-column grid instead of absolute positioning because:
- Scales naturally across screen sizes
- Easier to maintain
- Better accessibility
- Prevents layout breaks
Customizable:
- Headline text
- Phone mockup image
- Analytics card text
- Connector line visibility
- Mountain opacity
Why we used grid:
// Good: Natural responsive layout
grid-cols-1 lg:grid-cols-2
// Avoid: Breaks on different screens
position: absolute; left: 300px; top: 500px;Location: components/AdventureSection.tsx
Purpose: Community CTA with mountain backdrop
Customizable:
- Headline text
- Button text
- Background image
- Parallax effect intensity
Parallax effect:
// Adjust scroll speed (lower = slower)
transform: `translateY(${scrollY * 0.3}px)` // 30% of scroll speedLocation: components/PartnersSection.tsx
Purpose: Build trust with brand logos
Customizable:
- Partner logos
- Number of partners shown
- Logo sizes
- Grid layout
Location: components/TestimonialsSection.tsx
Purpose: Social proof from users
Customizable:
- Testimonial text
- User names
- User photos
- Number of testimonials
We built mobile layouts first, then enhanced for larger screens. This ensures the site works on all devices.
sm: 640px (Small tablets)
md: 768px (Tablets)
lg: 1024px (Laptops) ← Primary breakpoint
xl: 1280px (Desktops)
2xl: 1536px (Large displays)
{/* Stacks vertically on mobile, horizontal on desktop */} {/* Only visible on screens ≥1024px */} {/* 36px on mobile, 60px on desktop */} {/* Less padding on mobile, more on desktop */}In Browser:
- Open Chrome DevTools (F12 or Cmd+Option+I)
- Click device toolbar icon (Cmd+Shift+M)
- Test at these widths:
- 375px (iPhone SE)
- 768px (iPad)
- 1024px (Laptop)
- 1440px (Desktop)
Common Issues:
- Text too small on mobile → Add responsive text classes
- Elements overlapping → Check negative margins and absolute positioning
- Horizontal scroll → Add
overflow-hiddento container - Images too large on mobile → Add
max-w-xs lg:max-w-sm
# Create optimized production build
npm run build
# Test production build locally
npm run startCost: 30-day free trial, then $20/month minimum for production sites Pros: Handles both frontend and backend together seamlessly, no splitting needed, scales well for business sites Best for: Professional deployment with expected traffic growth
- Push code to GitHub
- Connect GitHub repo to Vercel
- Vercel auto-deploys on every push
- Custom domain setup available
- Push code to GitHub
- Connect repo to Netlify
- Build command:
npm run build - Publish directory:
.next
- Run
npm run build - Upload
.nextfolder to server - Ensure Node.js is installed on server
- Run
npm run start
This option splits the application into two deployments: frontend on Vercel and backend on DigitalOcean. This approach is necessary because Vercel's serverless architecture is not compatible with our Express.js backend without significant refactoring.
Prerequisites:
- GitHub repository with the code
- Vercel account (sign up at vercel.com)
- DigitalOcean account (sign up at digitalocean.com)
- Domain access for flowmersion.com
Step 1: Prepare Your Repository
Step 2: Connect to Vercel
- Log in to Vercel
- Click "Add New Project"
- Import your GitHub repository
- Select the repository containing your frontend code
Step 3: Configure Build Settings
- Root Directory: Set to frontend (if your Next.js app is in a subdirectory)
- Framework Preset: Next.js (should auto-detect)
- Build Command: npm run build (auto-detected)
- Output Directory: .next (auto-detected)
- Install Command: npm install (auto-detected)
Step 4: Set Environment Variables
In the Vercel dashboard, add the following environment variable:
NEXT_PUBLIC_API_URL=https://your-backend-url.com
(You'll update this URL after deploying the backend to DigitalOcean)
Step 5: Deploy
- Click "Deploy"
- Vercel will build and deploy your frontend
- You'll receive a temporary URL like your-project.vercel.app
- Test the deployment to ensure the frontend loads correctly
Step 6: Custom Domain Setup
- In Vercel dashboard, go to your project settings
- Navigate to "Domains"
- Add flowmersion.com and www.flowmersion.com
- Vercel will provide DNS configuration instructions
- Log into your domain registrar (where flowmersion.com was purchased)
- Update DNS records as instructed by Vercel:
Add an A record or CNAME pointing to Vercel's servers
Wait 24-48 hours for DNS propagation
Vercel will automatically provision an SSL certificate
Part B: Backend Deployment (DigitalOcean)
Step 1: Create a Droplet
- Log in to DigitalOcean
- Click "Create" → "Droplets"
- Choose an image: Ubuntu 22.04 LTS (recommended)
- Choose a plan: Basic ($4-6/month is sufficient for starting out)
- Choose a datacenter region close to your target audience
- Authentication: Add your SSH key or create a root password
- Click "Create Droplet"
Step 2: Connect to Your Droplet
ssh root@your_droplet_ip
Step 3: Install Node.js and npm
# Update package list
apt update && apt upgrade -y
# Install Node.js (v20.x recommended)
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
# Verify installation
node -v
npm -v
Step 4: Install PM2 (Process Manager)
npm install -g pm2
Step 5: Set Up Your Backend Application
# Create application directory
mkdir -p /var/www/snowin-backend
cd /var/www/snowin-backend
# Clone your repository (or use git)
git clone https://github.com/your-repo.git .
# Navigate to backend directory
cd backend
# Install dependencies
npm install
Step 6: Configure Environment Variables
# Create .env file
nano .env
Add your environment variables:
SUPABASE_URL=your_supabase_url
SUPABASE_SERVICE_ROLE_KEY=your_supabase_key
SUPABASE_KEY=your_supabase_key
WEBHOOK_SECRET=your_webhook_secret
NODE_ENV=production
PORT=8080
Save and exit (Ctrl+X, then Y, then Enter) Step 7: Update CORS Configuration Edit your server.js to allow requests from your Vercel frontend:
const corsOption = {
origin: [
"http://localhost:5173",
"http://localhost:3000",
"https://your-project.vercel.app",
"https://flowmersion.com",
"https://www.flowmersion.com"
],
credentials: true
};
app.use(cors(corsOption));
Step 8: Start the Backend with PM2
# Start the application
pm2 start server.js --name snowin-backend
# Save PM2 configuration
pm2 save
# Set PM2 to start on system boot
pm2 startup
# Follow the command output instructions
Step 2: Redeploy Frontend
- In Vercel dashboard, go to "Deployments"
- Click the three dots on the latest deployment
- Select "Redeploy"
- This will rebuild with the new environment variable
Step 3: Test the Integration
- Visit your Vercel frontend URL or flowmersion.com
- Try submitting the waitlist form
- Check that data is properly saved to Supabase
- Verify MailerLite integration is working
If you need API keys or secrets:
# Create .env.local file
NEXT_PUBLIC_API_URL=your_api_url
NEXT_PUBLIC_ANALYTICS_ID=your_idAccess in code:
const apiUrl = process.env.NEXT_PUBLIC_API_URL# Clear cache and reinstall
rm -rf node_modules
rm package-lock.json
npm installCheck:
- Image is in
/public/images/folder - Path starts with
/images/not./images/ - File extension is correct (.jpg, .png, .svg)
Solutions:
- Restart dev server (
npm run dev) - Check
tailwind.config.jsincludes your component path - Make sure class name is valid Tailwind utility
// tailwind.config.js
content: [
'./src/**/*.{js,ts,jsx,tsx}', // Must include your components
]Common fixes:
- Add
overflow-hiddento container - Use
max-w-fullon images - Check for fixed widths (use
w-full max-w-xsinstead) - Remove absolute positioning in favor of flex/grid
Solution: Run production build locally to test
npm run build
npm run startIgnore for deployment (not recommended):
npm run build -- --no-type-checkBetter: Fix the errors:
- Read error message carefully
- Add type annotations
- Use
anyas last resort:const data: any = ...
Always use Next.js <Image> component:
import Image from 'next/image'
// Optimized
<Image
src="/images/hero.jpg"
alt="Description"
width={1200}
height={800}
priority // For above-the-fold images
/>Images below the fold automatically lazy load with Next.js Image component.
For custom components:
const HeavyComponent = lazy(() => import('./HeavyComponent'))Fonts are preloaded in layout.tsx:
import { Manrope, Inter } from 'next/font/google'
const manrope = Manrope({ subsets: ['latin'] })
const inter = Inter({ subsets: ['latin'] })# Check for outdated packages
npm outdated
# Update all packages
npm update
# Update specific package
npm install react@latest# Update a specific package to latest version
npm install react@latest
# Update to a specific version
npm install next@15.5.4
# Update dev dependencies
npm install --save-dev typescript@latest
- Create component:
components/NewSection.tsx - Import in
app/page.tsx - Add to page layout
- Test responsive behavior
// app/page.tsx
import NewSection from '@/components/NewSection'
export default function Home() {
return (
<>
{/* Your new section */}
{/* ... other sections */}
</>
)
}- Official Cheatsheet
- Browse classes visually
npm run dev # Start development
npm run build # Build for production
npm run start # Run production build
npm run lint # Check code quality
npm install [pkg] # Add package
npm update # Update dependenciesv1.0.0 - December 2025
- Initial release
- Responsive design complete
- All sections implemented
- Production-ready (needs to be deployed)