Skip to content

Commit 25eaebd

Browse files
semsem-devclaude
andauthored
chore: upgrade dependencies (Next.js 16, React 19, Tailwind CSS 4, ESLint 10) (#63)
Major upgrades - next 14.2.3 -> 16.4, react / react-dom 18 -> 19.3 - tailwindcss 3.3 -> 4.3 (official upgrade tool: config moved into CSS, @tailwindcss/postcss, utility renames); autoprefixer removed - eslint 8 -> 10 with eslint-config-next 16 flat config (eslint.config.mjs); build runs "eslint . && next build" since Next 16 removed `next lint` - framer-motion 10 -> 14, react-icons 4 -> 5, next-themes 0.2 -> 0.4, react-github-calendar 4 -> 5, react-refractor 2 -> 4, @giscus/react 2 -> 3, sharp 0.32 -> 0.35, @vercel/analytics 1 -> 2, typescript 5.8 -> 5.9, postcss 8.5, styled-components 6.5 - CI: Node 20 (EOL) -> Node 24 Code changes required by the upgrades - Use `@theme inline` so font utilities resolve next/font CSS variables - Move the prism.css import to the top of globals.css (valid CSS @import) - Replace setState-in-effect mount checks with a useSyncExternalStore-based useHasMounted hook (React 19 lint rules) - Adapt to API changes: GitHubCalendar named export, typed framer-motion Variants, Slide props, react-refractor/refractor v5 imports Supersedes Dependabot PRs #45 #47 #48 #51 #55 #57 #58 #60 #61 #62. Remaining npm audit findings (5 high) are a single lint-time chain (eslint-config-next -> fast-glob -> micromatch -> braces) with no patched braces release yet; nothing ships to the static site. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 672a78b commit 25eaebd

21 files changed

Lines changed: 4495 additions & 3544 deletions

‎.eslintrc.json‎

Lines changed: 0 additions & 7 deletions
This file was deleted.

‎.github/workflows/deploy.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: Setup Node.js
2525
uses: actions/setup-node@v4
2626
with:
27-
node-version: "20"
27+
node-version: "24"
2828
cache: "npm"
2929

3030
- name: Install dependencies

‎app/animation/Slide.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"use client";
2-
import { motion, useInView, useAnimation, AnimationProps } from "framer-motion";
2+
import { motion, useInView, useAnimation } from "framer-motion";
33
import { useRef, useEffect, RefObject } from "react";
44

5-
interface SlideProps extends AnimationProps {
5+
interface SlideProps {
66
children: React.ReactNode;
77
delay?: number;
88
className?: string;

‎app/components/global/MobileMenu.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function MobileMenu() {
3737
<RxHamburgerMenu className="text-xl" />
3838
</button>
3939
<div
40-
className={`md:hidden fixed left-0 top-0 z-10 h-full w-full transform duration-[600ms] ease-[cubic-bezier(0.7,0,0,1)] dark:bg-zinc-900 bg-white ${navShow ? "translate-x-0 rounded-none" : "translate-x-full"
40+
className={`md:hidden fixed left-0 top-0 z-10 h-full w-full transform duration-600 ease-[cubic-bezier(0.7,0,0,1)] dark:bg-zinc-900 bg-white ${navShow ? "translate-x-0 rounded-none" : "translate-x-full"
4141
}`}
4242
>
4343
<div className="flex items-center justify-between mt-6 px-8">
@@ -48,7 +48,7 @@ export default function MobileMenu() {
4848
<button
4949
aria-label="Toggle Menu"
5050
onClick={onToggleNav}
51-
className={`md:hidden dark:bg-primary-bg bg-secondary-bg border dark:border-zinc-800 border-zinc-200 rounded-full p-2 duration-500 ${!navShow ? "-rotate-[360deg]" : null
51+
className={`md:hidden dark:bg-primary-bg bg-secondary-bg border dark:border-zinc-800 border-zinc-200 rounded-full p-2 duration-500 ${!navShow ? "rotate-[-360deg]" : null
5252
}`}
5353
>
5454
<HiOutlineX className="text-xl" />
@@ -63,7 +63,7 @@ export default function MobileMenu() {
6363
onClick={onToggleNav}
6464
>
6565
<link.icon
66-
className="text-zinc-500 group-hover:dark:text-white group-hover:text-zinc-800 duration-300"
66+
className="text-zinc-500 dark:group-hover:text-white group-hover:text-zinc-800 duration-300"
6767
aria-hidden="true"
6868
/>
6969
{link.title}

‎app/components/global/Theme.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
"use client";
2-
import { useState, useEffect } from "react";
32
import { useTheme } from "next-themes";
43
import { motion, AnimatePresence } from "framer-motion";
54
import SunIcon from "../../assets/icons/SunIcon";
65
import MoonIcon from "../../assets/icons/MoonIcon";
6+
import { useHasMounted } from "../../utils/use-has-mounted";
77

88
export default function Theme() {
99
const { systemTheme, theme, setTheme } = useTheme();
10-
const [hasMounted, setHasMounted] = useState(false);
10+
const hasMounted = useHasMounted();
1111
const currentTheme = theme === "system" ? systemTheme : theme;
1212

1313
function toggleTheme() {
1414
return currentTheme === "light" ? setTheme("dark") : setTheme("light");
1515
}
16-
useEffect(() => setHasMounted(true), []);
1716

1817
if (!hasMounted)
1918
return (

‎app/components/pages/ContributionGraph.tsx‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"use client";
22
import { useTheme } from "next-themes";
3-
import GitHubCalendar from "react-github-calendar";
3+
import { GitHubCalendar } from "react-github-calendar";
44
import { github } from "@/app/data/contribution-graph-theme";
5-
import { useState, useEffect } from "react";
5+
import { useState } from "react";
6+
import { useHasMounted } from "@/app/utils/use-has-mounted";
67
import YearButton from "../shared/YearButton";
78
import { getGitHubYears } from "@/app/utils/calculate-years";
89
import EmptyState from "../shared/EmptyState";
@@ -13,17 +14,13 @@ export default function ContributionGraph() {
1314
undefined
1415
);
1516
const { theme, systemTheme } = useTheme();
16-
const [serverTheme, setServerTheme] = useState<"light" | "dark" | undefined>(
17-
undefined
18-
);
1917
const scheme =
2018
theme === "light" ? "light" : theme === "dark" ? "dark" : systemTheme;
2119

22-
// Set theme only after rendering to avoid mismatch between client and server
20+
// Apply the theme only after hydration to avoid a client/server mismatch
2321
// https://github.com/vercel/next.js/issues/10608#issuecomment-589073831
24-
useEffect(() => {
25-
setServerTheme(scheme);
26-
}, [scheme]);
22+
const hasMounted = useHasMounted();
23+
const serverTheme = hasMounted ? scheme : undefined;
2724

2825
const today = new Date().getFullYear();
2926
const username = "semsem-dev";

‎app/components/pages/Job.tsx‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export default async function Job() {
4747
<div className="flex flex-col font-mono text-xs sm:text-sm">
4848
{/* Line 1 */}
4949
<div className="flex gap-4">
50-
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none flex-shrink-0">1</span>
50+
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none shrink-0">1</span>
5151
<div className="flex flex-wrap gap-x-2">
5252
<span className="text-purple-500 font-semibold">const</span>
5353
<span className="text-blue-500 font-semibold">experience</span>
@@ -58,7 +58,7 @@ export default async function Job() {
5858

5959
{/* Line 2 */}
6060
<div className="flex gap-4">
61-
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none flex-shrink-0">2</span>
61+
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none shrink-0">2</span>
6262
<div className="pl-4 flex flex-wrap gap-x-2">
6363
<span className="text-red-400">company:</span>
6464
<span className="text-green-500">"{job.name}"</span>,
@@ -67,12 +67,12 @@ export default async function Job() {
6767

6868
{/* Line 3 */}
6969
<div className="flex gap-4">
70-
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none flex-shrink-0">3</span>
70+
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none shrink-0">3</span>
7171
<div className="pl-4 flex flex-wrap gap-x-2 items-center">
7272
<span className="text-red-400">role:</span>
7373
<div className="flex items-center gap-2">
7474
{job.logo && (
75-
<div className="relative w-4 h-4 rounded-sm overflow-hidden flex-shrink-0">
75+
<div className="relative w-4 h-4 rounded-xs overflow-hidden shrink-0">
7676
<Image src={job.logo} fill alt={job.name} className="object-cover" />
7777
</div>
7878
)}
@@ -83,7 +83,7 @@ export default async function Job() {
8383

8484
{/* Line 4 */}
8585
<div className="flex gap-4">
86-
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none flex-shrink-0">4</span>
86+
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none shrink-0">4</span>
8787
<div className="pl-4 flex flex-wrap gap-x-2">
8888
<span className="text-red-400">period:</span>
8989
<span className="text-yellow-500">
@@ -94,26 +94,26 @@ export default async function Job() {
9494

9595
{/* Line 5 - Description (Wraps) */}
9696
<div className="flex gap-4">
97-
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none flex-shrink-0">5</span>
97+
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none shrink-0">5</span>
9898
<div className="pl-4 flex flex-wrap gap-x-2">
9999
<span className="text-red-400">description:</span>
100-
<span className="text-zinc-500 dark:text-zinc-400 italic break-words whitespace-pre-wrap">
100+
<span className="text-zinc-500 dark:text-zinc-400 italic wrap-break-word whitespace-pre-wrap">
101101
// {job.description}
102102
</span>
103103
</div>
104104
</div>
105105

106106
{/* Line 6 */}
107107
<div className="flex gap-4">
108-
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none flex-shrink-0">6</span>
108+
<span className="w-6 text-right text-zinc-300 dark:text-zinc-700 select-none shrink-0">6</span>
109109
<div className="text-zinc-600 dark:text-zinc-300">{"}"}</div>
110110
</div>
111111
</div>
112112

113113
{/* Link Overlay */}
114114
<RefLink
115115
href={job.url}
116-
className="absolute inset-0 z-10 focus:outline-none focus:ring-2 focus:ring-blue-500/50 rounded-lg"
116+
className="absolute inset-0 z-10 focus:outline-hidden focus:ring-2 focus:ring-blue-500/50 rounded-lg"
117117
>
118118
<span className="sr-only">View {job.name}</span>
119119
</RefLink>

‎app/components/pages/Testimonials.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default function Testimonials() {
5050
href={LINKEDIN_RECOMMENDATIONS_URL}
5151
target="_blank"
5252
rel="noopener noreferrer"
53-
className="relative p-6 rounded-2xl dark:bg-zinc-800/50 bg-zinc-50 border dark:border-zinc-700/50 border-zinc-200 backdrop-blur-sm transition-all duration-300 group cursor-pointer block"
53+
className="relative p-6 rounded-2xl dark:bg-zinc-800/50 bg-zinc-50 border dark:border-zinc-700/50 border-zinc-200 backdrop-blur-xs transition-all duration-300 group cursor-pointer block"
5454
>
5555
<BiSolidQuoteAltLeft className="absolute top-4 right-4 text-3xl dark:text-zinc-700 text-zinc-300 group-hover:text-blue-500/50 transition-colors duration-300" />
5656

‎app/components/pages/Usage.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ export default function Usage() {
5353
boxShadow: "0 4px 20px -4px rgba(59, 130, 246, 0.3)"
5454
}}
5555
>
56-
<span className="group relative px-4 py-2 bg-zinc-100 dark:bg-zinc-800/80 border border-zinc-200 dark:border-zinc-700/50 rounded-xl text-sm text-zinc-700 dark:text-zinc-300 cursor-default transition-all duration-300 hover:border-blue-500/50 hover:dark:border-blue-500/50 overflow-hidden inline-block">
56+
<span className="group relative px-4 py-2 bg-zinc-100 dark:bg-zinc-800/80 border border-zinc-200 dark:border-zinc-700/50 rounded-xl text-sm text-zinc-700 dark:text-zinc-300 cursor-default transition-all duration-300 hover:border-blue-500/50 dark:hover:border-blue-500/50 overflow-hidden inline-block">
5757
{/* Gradient overlay on hover */}
58-
<span className="absolute inset-0 bg-gradient-to-r from-blue-500/0 via-blue-500/5 to-purple-500/0 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
58+
<span className="absolute inset-0 bg-linear-to-r from-blue-500/0 via-blue-500/5 to-purple-500/0 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
5959
<span className="relative z-10 font-medium">{item}</span>
6060
</span>
6161
</motion.span>

‎app/components/pages/WorldMap.tsx‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { visitedCountries } from "@/app/data/travel";
44
import { Slide } from "../../animation/Slide";
5-
import { motion, useInView, AnimatePresence } from "framer-motion";
5+
import { motion, useInView, AnimatePresence, type Variants } from "framer-motion";
66
import { useState, useRef, useEffect } from "react";
77
import { BiWorld } from "react-icons/bi";
88
import { FaPlane, FaChevronDown, FaChevronLeft, FaChevronRight } from "react-icons/fa";
@@ -18,7 +18,7 @@ const countryRegions: Record<string, string> = {
1818
};
1919

2020
// Animation variants for staggered children
21-
const containerVariants = {
21+
const containerVariants: Variants = {
2222
hidden: { opacity: 0 },
2323
visible: {
2424
opacity: 1,
@@ -29,7 +29,7 @@ const containerVariants = {
2929
}
3030
};
3131

32-
const cardVariants = {
32+
const cardVariants: Variants = {
3333
hidden: {
3434
opacity: 0,
3535
y: 30,
@@ -50,7 +50,7 @@ const cardVariants = {
5050
}
5151
};
5252

53-
const statCardVariants = {
53+
const statCardVariants: Variants = {
5454
hidden: { opacity: 0, y: 40, scale: 0.8 },
5555
visible: (i: number) => ({
5656
opacity: 1,
@@ -65,7 +65,7 @@ const statCardVariants = {
6565
})
6666
};
6767

68-
const filterPillVariants = {
68+
const filterPillVariants: Variants = {
6969
idle: { scale: 1 },
7070
hover: {
7171
scale: 1.08,
@@ -201,7 +201,7 @@ export default function WorldMap() {
201201
}}
202202
transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }}
203203
>
204-
<div className="absolute -top-24 -right-24 w-48 h-48 bg-gradient-to-br from-blue-500/10 to-purple-500/10 rounded-full blur-3xl" />
204+
<div className="absolute -top-24 -right-24 w-48 h-48 bg-linear-to-br from-blue-500/10 to-purple-500/10 rounded-full blur-3xl" />
205205
</motion.div>
206206
<motion.div
207207
animate={{
@@ -211,7 +211,7 @@ export default function WorldMap() {
211211
}}
212212
transition={{ duration: 10, repeat: Infinity, ease: "easeInOut" }}
213213
>
214-
<div className="absolute -bottom-24 -left-24 w-48 h-48 bg-gradient-to-br from-pink-500/10 to-orange-500/10 rounded-full blur-3xl" />
214+
<div className="absolute -bottom-24 -left-24 w-48 h-48 bg-linear-to-br from-pink-500/10 to-orange-500/10 rounded-full blur-3xl" />
215215
</motion.div>
216216

217217
{/* Stats Cards with animated counters */}
@@ -327,7 +327,7 @@ export default function WorldMap() {
327327
>
328328
<button
329329
onClick={() => scrollGrid('left')}
330-
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 p-2 rounded-full bg-white/80 dark:bg-zinc-800/80 backdrop-blur-sm shadow-lg border dark:border-zinc-700 border-zinc-200 sm:hidden"
330+
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 p-2 rounded-full bg-white/80 dark:bg-zinc-800/80 backdrop-blur-xs shadow-lg border dark:border-zinc-700 border-zinc-200 sm:hidden"
331331
>
332332
<FaChevronLeft className="text-zinc-600 dark:text-zinc-300" />
333333
</button>
@@ -344,7 +344,7 @@ export default function WorldMap() {
344344
>
345345
<button
346346
onClick={() => scrollGrid('right')}
347-
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 p-2 rounded-full bg-white/80 dark:bg-zinc-800/80 backdrop-blur-sm shadow-lg border dark:border-zinc-700 border-zinc-200 sm:hidden"
347+
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 p-2 rounded-full bg-white/80 dark:bg-zinc-800/80 backdrop-blur-xs shadow-lg border dark:border-zinc-700 border-zinc-200 sm:hidden"
348348
>
349349
<FaChevronRight className="text-zinc-600 dark:text-zinc-300" />
350350
</button>
@@ -382,7 +382,7 @@ export default function WorldMap() {
382382
className="group relative flex flex-col p-3 rounded-lg bg-zinc-50 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700 hover:border-blue-400 dark:hover:border-blue-500 transition-colors"
383383
>
384384
<div className="flex items-center gap-2 mb-2">
385-
<div className="relative w-5 h-3.5 rounded-[2px] overflow-hidden shadow-sm">
385+
<div className="relative w-5 h-3.5 rounded-[2px] overflow-hidden shadow-xs">
386386
<Image
387387
src={`https://flagcdn.com/w40/${country.code}.png`}
388388
alt={country.name}
@@ -447,7 +447,7 @@ export default function WorldMap() {
447447
animate={{ opacity: [0.5, 1, 0.5] }}
448448
transition={{ duration: 2, repeat: Infinity }}
449449
>
450-
<span className="text-xs text-zinc-500 dark:text-zinc-400 font-medium px-3 py-1 rounded-full bg-white/80 dark:bg-zinc-800/80 backdrop-blur-sm">
450+
<span className="text-xs text-zinc-500 dark:text-zinc-400 font-medium px-3 py-1 rounded-full bg-white/80 dark:bg-zinc-800/80 backdrop-blur-xs">
451451
Scroll to explore
452452
</span>
453453
</motion.span>
@@ -459,7 +459,7 @@ export default function WorldMap() {
459459
ease: "easeInOut"
460460
}}
461461
>
462-
<div className="p-2 rounded-full bg-gradient-to-br from-blue-500/20 to-purple-500/20 backdrop-blur-sm border border-zinc-200/50 dark:border-zinc-700/50">
462+
<div className="p-2 rounded-full bg-linear-to-br from-blue-500/20 to-purple-500/20 backdrop-blur-xs border border-zinc-200/50 dark:border-zinc-700/50">
463463
<motion.div
464464
animate={{
465465
opacity: [0.5, 1, 0.5],

0 commit comments

Comments
 (0)