Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
327202f
chore: Update Remix dependencies to latest versions
koriigami Oct 1, 2025
d8639a5
fix: Configure ESM build and add MDX type definitions
koriigami Oct 3, 2025
cf95a63
refactor: Migrate project from Cloudflare to Vercel
koriigami Oct 3, 2025
1fc7544
fix: Defer client-side component rendering to prevent hydration error
koriigami Oct 3, 2025
1e806dd
fix: Correctly use ClientOnly to prevent hydration errors
koriigami Oct 3, 2025
4be6380
fix: Remove unused hooks and correct image component
koriigami Oct 3, 2025
d25d8ee
fix: Make useWindowSize hook SSR-safe to resolve production build fai…
koriigami Oct 3, 2025
60d281d
chore: Fully migrate project from Cloudflare to Vercel build system
koriigami Oct 3, 2025
5bbf2ec
refactor: Finalize migration from Cloudflare Pages to Vercel architec…
koriigami Oct 6, 2025
fa3dc6f
chore: Remove legacy Vercel configuration to enable auto-detection
koriigami Oct 6, 2025
f346cf6
fix: Implement SSR-safe hooks and finalize build configuration
koriigami Oct 6, 2025
656f60c
fix: Restore postinstall script to include Draco decoder files
koriigami Oct 6, 2025
04ce798
fix: Downgrade remix-utils to resolve peer dependency conflict
koriigami Oct 6, 2025
b6983b5
fix: Synchronize Node.js environment and regenerate lockfile
koriigami Oct 6, 2025
3fc1e85
fix: Configure project for Vercel deployment and fix 3D model loading
claude Oct 29, 2025
1fea332
feat: Update personal information in config.json
claude Oct 29, 2025
cc390ce
feat: Update homepage with Ketan's bio and experience
claude Oct 29, 2025
8044184
feat: Update homepage featured projects
claude Oct 29, 2025
7eb95ea
feat: Create All Projects listing page and update navigation
claude Oct 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ node_modules
.wrangler
.dev.vars

public/draco
storybook-static
1 change: 0 additions & 1 deletion .node-version

This file was deleted.

1 change: 0 additions & 1 deletion .npmrc

This file was deleted.

132 changes: 132 additions & 0 deletions VERCEL_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Vercel Deployment Setup

This document explains how to deploy this portfolio website to Vercel.

## Required Environment Variables

You need to set these environment variables in your Vercel project dashboard:

### Essential Variables

1. **SESSION_SECRET** (Required for dark mode to work)
- Purpose: Used to encrypt session cookies for theme persistence
- Value: Any random string (at least 32 characters recommended)
- Example: Generate with: `openssl rand -base64 32`

### Email Functionality (Optional - only needed if using contact form)

2. **ENVIRONMENT**
- Value: `production`

3. **EMAIL**
- Your recipient email address

4. **FROM_EMAIL**
- AWS SES verified sender email address

5. **AWS_ACCESS_KEY_ID**
- AWS credentials for SES email service

6. **AWS_SECRET_ACCESS_KEY**
- AWS credentials for SES email service

## Deployment Steps

1. **Install Vercel CLI** (optional)
```bash
npm i -g vercel
```

2. **Push to GitHub**
- Commit and push your changes to GitHub
- Vercel will auto-detect the Remix framework

3. **Connect to Vercel**
- Go to https://vercel.com
- Import your GitHub repository
- Vercel will automatically detect the Remix configuration

4. **Set Environment Variables**
- Go to Project Settings → Environment Variables
- Add `SESSION_SECRET` (minimum required)
- Add other variables if using email functionality

5. **Deploy**
- Vercel will automatically deploy on push to your main branch
- The build command is: `npm install && npm run build`
- The postinstall script will automatically copy Draco decoder files

## Key Fixes Applied

### 1. 3D Models Issue - Fixed
- **Problem**: Draco decoder files were in `.gitignore`, preventing 3D models from loading
- **Solution**: Removed `public/draco` from `.gitignore` and updated the postinstall script to use native Node.js `fs` module

### 2. Dark Mode Issue - Fixed
- **Problem**: Without `SESSION_SECRET`, theme cookies don't work properly
- **Solution**: Set `SESSION_SECRET` environment variable in Vercel

### 3. Font Loading - Fixed
- **Problem**: Fonts not loading correctly
- **Solution**: Fonts are properly bundled by Vite and injected via CSS @font-face declarations

## Troubleshooting

### 3D Models Not Appearing
- Ensure `public/draco/` directory is committed to git
- Check browser console for DRACO-related errors
- Verify cache headers in `public/_headers` file

### Dark Mode White Background
- Verify `SESSION_SECRET` is set in Vercel environment variables
- Check browser cookies are enabled
- Clear browser cache and cookies

### Wrong Fonts Loading
- Check browser Network tab to ensure .woff2 files are loading
- Verify fonts are in the build output
- Check for CORS issues in browser console

### Build Failures
- Ensure Node.js version is set to 20.x
- Check that all dependencies are installed
- Review Vercel build logs for specific errors

## Local Testing

To test locally before deploying:

```bash
# Install dependencies
npm install

# Run development server
npm run dev

# Build for production
npm run build

# Preview production build
npm start
```

Visit `http://localhost:7777` for development or `http://localhost:3000` for production preview.

## Performance Optimizations

The following optimizations are configured:

- **Draco compression** for 3D models (reduces size by ~60%)
- **Long-term caching** for static assets (1 year)
- **Font preloading** for critical fonts (Gotham Medium and Book)
- **Progressive image loading** with placeholders
- **Code splitting** via Vite
- **Asset hashing** for cache busting

## Support

If you encounter issues:
1. Check Vercel build logs
2. Review browser console for errors
3. Verify all environment variables are set correctly
4. Ensure you're using Node.js 20.x
4 changes: 2 additions & 2 deletions app/components/image/image.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button } from '~/components/button';
import { Icon } from '~/components/icon';
import { useTheme } from '~/components/theme-provider';
import { useReducedMotion } from 'framer-motion';
import { useHasMounted, useInViewport } from '~/hooks';
import { useMounted, useInViewport } from '~/hooks';
import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
import { resolveSrcFromSrcSet } from '~/utils/image';
import { classes, cssProps, numToMs } from '~/utils/style';
Expand Down Expand Up @@ -82,7 +82,7 @@ const ImageElements = ({
const videoRef = useRef();
const isVideo = getIsVideo(src);
const showFullRes = inViewport;
const hasMounted = useHasMounted();
const hasMounted = useMounted();

useEffect(() => {
const resolveVideoSrc = async () => {
Expand Down
2 changes: 1 addition & 1 deletion app/components/transition/transition.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,4 @@ const TransitionContent = ({
}, [isPresent, onExit, safeToRemove, timeout, onExited, show]);

return children({ visible, status, nodeRef });
};
};
20 changes: 11 additions & 9 deletions app/config.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{
"name": "Hamish Williams",
"role": "Designer",
"disciplines": ["Developer", "Prototyper", "Animator", "Illustrator", "Modder"],
"url": "https://hamishw.com",
"bluesky": "hamishw.com",
"figma": "@Hamish",
"github": "HamishMW",
"repo": "https://github.com/HamishMW/portfolio",
"ascii": "__ __ __\n\u005C \u005C \u005C \u005C \u005C\u2215\n \u005C \u005C\u2215\u005C \u005C\n \u005C\u2215 \u005C\u2215\n"
"name": "Ketan Damle",
"role": "UX/UI Designer",
"disciplines": ["UX Designer", "Product Designer", "Design Systems", "Prototyper", "Interaction Designer"],
"url": "https://ketandamle.com",
"dribbble": "https://dribbble.com/koriigami",
"medium": "https://medium.com/@koriigami",
"figma": "https://www.figma.com/@Kagadmodyaa",
"instagram": "https://www.instagram.com/kagadmodyaaa/",
"github": "koriigami",
"repo": "https://github.com/koriigami/portfolio_v2",
"ascii": "__ __ __\n\\ \\ \\ \\ \\/\n \\ \\/\\ \\\n \\/ \\/\n"
}
2 changes: 1 addition & 1 deletion app/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export * from './useFormInput';
export * from './useHasMounted';
export * from './useInterval';
export * from './useInViewport';
export * from './useParallax';
export * from './usePrevious';
export * from './useScrollToHash';
export * from './useWindowSize';
export * from './useMounted';
13 changes: 0 additions & 13 deletions app/hooks/useHydrated.js

This file was deleted.

6 changes: 3 additions & 3 deletions app/hooks/useHasMounted.js → app/hooks/useMounted.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useEffect, useState } from 'react';
import { useState, useEffect } from 'react';

export function useHasMounted() {
export function useMounted() {
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

return mounted;
}
}
67 changes: 18 additions & 49 deletions app/hooks/useWindowSize.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,29 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useState, useEffect } from 'react';

export function useWindowSize() {
const dimensions = useRef(() => ({ w: 1280, h: 800 }));

const createRuler = useCallback(() => {
let ruler = document.createElement('div');

ruler.style.position = 'fixed';
ruler.style.height = '100vh';
ruler.style.width = 0;
ruler.style.top = 0;

document.documentElement.appendChild(ruler);

// Set cache conscientious of device orientation
dimensions.current.w = window.innerWidth;
dimensions.current.h = ruler.offsetHeight;
const isBrowser = typeof window !== 'undefined';

// Clean up after ourselves
document.documentElement.removeChild(ruler);
ruler = null;
}, []);
function getSize() {
return {
width: isBrowser ? window.innerWidth : 1280, // Default width for SSR
height: isBrowser ? window.innerHeight : 800, // Default height for SSR
};
}

// Get the actual height on iOS Safari
const getHeight = useCallback(() => {
const isIOS = navigator?.userAgent.match(/iphone|ipod|ipad/i);
export function useWindowSize() {
const [windowSize, setWindowSize] = useState(getSize);

if (isIOS) {
createRuler();
return dimensions.current.h;
useEffect(() => {
if (!isBrowser) {
return; // Don't add event listener on the server
}

return window.innerHeight;
}, [createRuler]);

const getSize = useCallback(() => {
return {
width: window.innerWidth,
height: getHeight(),
};
}, [getHeight]);

const [windowSize, setWindowSize] = useState(dimensions.current);

useEffect(() => {
const handleResize = () => {
function handleResize() {
setWindowSize(getSize());
};
}

window.addEventListener('resize', handleResize);
handleResize();

return () => {
window.removeEventListener('resize', handleResize);
};
}, [getSize]);
return () => window.removeEventListener('resize', handleResize);
}, []);

return windowSize;
}
}
22 changes: 16 additions & 6 deletions app/layouts/navbar/nav-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import config from '~/config.json';
export const navLinks = [
{
label: 'Projects',
pathname: '/#project-1',
pathname: '/projects',
},
{
label: 'Details',
label: 'About',
pathname: '/#details',
},
{
Expand All @@ -21,15 +21,25 @@ export const navLinks = [

export const socialLinks = [
{
label: 'Bluesky',
url: `https://bsky.app/profile/${config.bluesky}`,
icon: 'bluesky',
label: 'Dribbble',
url: config.dribbble,
icon: 'dribbble',
},
{
label: 'Medium',
url: config.medium,
icon: 'medium',
},
{
label: 'Figma',
url: `https://www.figma.com/${config.figma}`,
url: config.figma,
icon: 'figma',
},
{
label: 'Instagram',
url: config.instagram,
icon: 'instagram',
},
{
label: 'Github',
url: `https://github.com/${config.github}`,
Expand Down
4 changes: 2 additions & 2 deletions app/root.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
useNavigation,
useRouteError,
} from '@remix-run/react';
import { createCookieSessionStorage, json } from '@remix-run/cloudflare';
import { createCookieSessionStorage, json } from '@remix-run/node';
import { ThemeProvider, themeStyles } from '~/components/theme-provider';
import GothamBook from '~/assets/fonts/gotham-book.woff2';
import GothamMedium from '~/assets/fonts/gotham-medium.woff2';
Expand Down Expand Up @@ -59,7 +59,7 @@ export const loader = async ({ request, context }) => {
maxAge: 604_800,
path: '/',
sameSite: 'lax',
secrets: [context.cloudflare.env.SESSION_SECRET || ' '],
secrets: [process.env.SESSION_SECRET || ' '],
secure: true,
},
});
Expand Down
4 changes: 2 additions & 2 deletions app/routes/api.set-theme.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { json, createCookieSessionStorage } from '@remix-run/cloudflare';
import { json, createCookieSessionStorage } from '@remix-run/node';

export async function action({ request, context }) {
const formData = await request.formData();
Expand All @@ -11,7 +11,7 @@ export async function action({ request, context }) {
maxAge: 604_800,
path: '/',
sameSite: 'lax',
secrets: [context.cloudflare.env.SESSION_SECRET || ' '],
secrets: [process.env.SESSION_SECRET || ' '],
secure: true,
},
});
Expand Down
2 changes: 1 addition & 1 deletion app/routes/articles/route.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { json } from '@remix-run/cloudflare';
import { json } from '@remix-run/node';
import { Outlet, useLoaderData } from '@remix-run/react';
import { MDXProvider } from '@mdx-js/react';
import { Post, postMarkdown } from '~/layouts/post';
Expand Down
Loading