Skip to content

Commit 748d9f3

Browse files
build(frontend): Next.js 16アップグレード後のビルド修復と依存整理 (#249)
## Why 依存アップグレード(Next.js 16 / React 19 / ESLint 9 フラットコンフィグ等)で `yarn build` / `yarn lint` / CI インストールが壊れていたので、ビルドチェーンを再び通るようにする。併せて調査中に判明した Tailwind / shadcn スタック一式や、役目を終えた依存の残骸も掃除する。 ## What ### Next.js 16 / ESLint 9 アップグレード対応 - `next lint` 廃止に伴う ESLint フラットコンフィグ移行(`eslint.config.mjs`) - Next.js 16 の purity lint / set-state-in-effect ルールに合わせた `GameTyping.tsx` / `RankingTabs.tsx` / `game/page.tsx` の修正 - `tsconfig.json` を Next.js の自動補正(`jsx: react-jsx` / `.next/dev/types/**` の include 追加)に追従 ### Tailwind / shadcn スタック撤去 実態として `@tailwind` ディレクティブも Tailwind ユーティリティクラスも使われていないため、関連パッケージ・設定ファイルを一式削除: - **dependencies** 削除: `clsx`, `tailwind-merge`, `tailwindcss-animate`, `@radix-ui/react-slot`, `@radix-ui/react-toast`, `class-variance-authority`, `lucide-react` - **devDependencies** 削除: `tailwindcss`, `@tailwindcss/postcss`, `postcss`, `autoprefixer` - **ファイル削除**: `tailwind.config.ts`, `postcss.config.js`, `src/libs/shadcn/` - autoprefixing は Next.js 組み込みの PostCSS パイプライン(`postcss-preset-env`)に委譲 ### 依存整備 - `@testing-library/react` の peer 要件である `@testing-library/dom` を明示追加 - `prettier` を `dependencies` → `devDependencies` に分類修正 - `@types/node` / `eslint` / `prettier` / `tsx` の patch / minor 追従 - `@next/codemod` が自動挿入した `@types/react` / `@types/react-dom` の `resolutions` ブロックを削除(React 19 エコシステム成熟により不要、むしろ古い `19.0.1` / `19.0.2` に固定される副作用が出ていた) - `openapi-typescript` v6 → v7 bump に合わせて `v1.d.ts` を v7 format で再生成 - `RankingTabs.tsx` の未使用 `useCallback` import 削除 - Yarn 4(Corepack)で `yarn.lock` をクリーン再生成 ## How - `tsconfig.json` の差分(`jsx` 変更・include 追加)は Next.js 16 が `next build` 時に自動で書き戻すもので、手動の設計変更ではない。ログ上も `jsx was set to react-jsx (next.js uses the React automatic runtime)` として明示される。 - `postcss.config.js` を削除することで Next.js 組み込みの PostCSS デフォルトにフォールバックする構成。最終出力 CSS(ベンダープレフィックス等)は従前と同じ。 - ESLint は `^9.39.4` 固定(v10 非対応): `eslint-plugin-react` が ESLint v10 で削除されたレガシー API(`context.getFilename()`)に依存しているため、上流修正 ([jsx-eslint/eslint-plugin-react#3979](jsx-eslint/eslint-plugin-react#3979)) が公開されるまで v9 に留める。参考: [vercel/next.js#89764](vercel/next.js#89764) - lint は 0 errors(既存の `<img>` 6 件と 1 件の `react-hooks/exhaustive-deps` 警告のみ残る。いずれも今回のスコープ外) ## Validation - `corepack yarn install --immutable` - `corepack yarn format:ci` - `corepack yarn lint` - `corepack yarn build` - `corepack yarn test --runInBand` ## References - [Next.js 16 upgrade guide](https://nextjs.org/docs/app/guides/upgrading/version-16) - [Next.js ESLint migration / flat config](https://nextjs.org/docs/app/api-reference/config/eslint) - [Next.js codemods](https://nextjs.org/docs/app/guides/upgrading/codemods) - [Tailwind CSS v4 upgrade guide](https://tailwindcss.com/docs/upgrade-guide) - [vercel/next.js#89764 — ESLint v10 incompatibility](vercel/next.js#89764) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0605784 commit 748d9f3

File tree

12 files changed

+2449
-3034
lines changed

12 files changed

+2449
-3034
lines changed

typing-app/.eslintrc.json

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

typing-app/eslint.config.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
2+
import eslintConfigPrettier from "eslint-config-prettier/flat";
3+
4+
const eslintConfig = [...nextCoreWebVitals, eslintConfigPrettier];
5+
6+
export default eslintConfig;

typing-app/package.json

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,57 +6,44 @@
66
"dev": "next dev --turbopack",
77
"build": "next build",
88
"start": "next start",
9-
"lint": "next lint",
9+
"lint": "eslint .",
1010
"format": "prettier . --write",
1111
"format:ci": "prettier . --check",
1212
"sanitize": "tsx scripts/sanitizeText.ts",
1313
"test": "jest",
1414
"test:watch": "jest --watch"
1515
},
1616
"dependencies": {
17-
"@radix-ui/react-slot": "^1.1.0",
18-
"@radix-ui/react-toast": "^1.2.2",
19-
"class-variance-authority": "^0.7.1",
20-
"clsx": "^2.1.1",
21-
"lucide-react": "^0.468.0",
22-
"next": "15.2.4",
23-
"openapi-fetch": "0.13.3",
24-
"prettier": "^3.4.2",
25-
"react": "19.0.0",
26-
"react-dom": "19.0.0",
17+
"next": "16.2.3",
18+
"openapi-fetch": "0.17.0",
19+
"react": "19.2.5",
20+
"react-dom": "19.2.5",
2721
"server-only": "^0.0.1",
28-
"sharp": "^0.33.5",
29-
"tailwind-merge": "^2.5.5",
30-
"tailwindcss-animate": "^1.0.7"
22+
"sharp": "^0.34.5"
3123
},
3224
"devDependencies": {
33-
"@jest/globals": "^29.7.0",
34-
"@testing-library/jest-dom": "^6.6.3",
35-
"@testing-library/react": "^14.3.1",
36-
"@types/node": "^20.17.10",
37-
"@types/react": "19.0.1",
38-
"@types/react-dom": "19.0.2",
39-
"autoprefixer": "^10.4.20",
40-
"eslint": "^8.57.1",
41-
"eslint-config-next": "15.1.0",
42-
"eslint-config-prettier": "^9.1.0",
43-
"jest": "^29.7.0",
44-
"jest-environment-jsdom": "^29.7.0",
45-
"openapi-typescript": "6.7.6",
46-
"postcss": "^8.4.49",
47-
"sass": "^1.85.0",
48-
"tailwindcss": "^3.4.16",
49-
"tsx": "^4.19.3",
50-
"typescript": "^5.7.2"
25+
"@jest/globals": "^30.3.0",
26+
"@testing-library/dom": "^10.4.1",
27+
"@testing-library/jest-dom": "^6.9.1",
28+
"@testing-library/react": "^16.3.2",
29+
"@types/node": "^25.6.0",
30+
"@types/react": "19.2.14",
31+
"@types/react-dom": "19.2.3",
32+
"eslint": "^9.39.4",
33+
"eslint-config-next": "16.2.3",
34+
"eslint-config-prettier": "^10.1.8",
35+
"jest": "^30.3.0",
36+
"jest-environment-jsdom": "^30.3.0",
37+
"openapi-typescript": "7.13.0",
38+
"prettier": "^3.8.2",
39+
"sass": "^1.99.0",
40+
"tsx": "^4.21.0",
41+
"typescript": "^6.0.2"
5142
},
5243
"repository": "https://github.com/su-its/typing",
5344
"author": "su-its",
5445
"packageManager": "yarn@4.6.0",
5546
"engines": {
5647
"npm": "use yarn instead"
57-
},
58-
"resolutions": {
59-
"@types/react": "19.0.1",
60-
"@types/react-dom": "19.0.2"
6148
}
6249
}

typing-app/postcss.config.js

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

typing-app/src/app/game/page.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import fs from "fs";
33

44
const filenames = fs.readdirSync("public/texts/");
55

6+
const getRandomSubjectText = () => {
7+
const randomFilename = filenames[Math.floor(Math.random() * filenames.length)] ?? filenames[0];
8+
return fs.readFileSync(`public/texts/${randomFilename}`, "utf-8");
9+
};
10+
611
export default function Typing() {
7-
const subjectText = fs.readFileSync(
8-
`public/texts/${filenames[Math.floor(Math.random() * filenames.length)]}`,
9-
"utf-8"
10-
);
12+
const subjectText = getRandomSubjectText();
1113
const subjectTextOneLine = subjectText.replace(/\n/gm, " ");
1214

1315
return <GamePage subjectText={subjectTextOneLine} />;

typing-app/src/components/organism/RankingTabs.tsx

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import RankingTable from "../organism/RankingTable";
44
import { Pagination } from "../molecules/Pagination";
55
import RefreshButton from "../atoms/RefreshButton";
6-
import { useCallback, useEffect, useState } from "react";
6+
import { useEffect, useState } from "react";
77
import { client } from "@/libs/api";
88
import type { components } from "@/libs/api/v1";
99
import { showErrorToast } from "@/utils/toast";
@@ -53,30 +53,42 @@ const RankingTabs = () => {
5353
const [rankingStartFrom, setRankingStartFrom] = useState(1);
5454
const [sortBy, setSortBy] = useState<"accuracy" | "keystrokes">("accuracy");
5555
const [totalRankingCount, setTotalRankingCount] = useState<number>(0);
56+
const [refreshKey, setRefreshKey] = useState(0);
5657

5758
const LIMIT = 10; //TODO: Configファイルから取得
5859

59-
const fetchData = useCallback(async () => {
60-
const { data, error } = await client.GET("/scores/ranking", {
61-
params: {
62-
query: {
63-
sort_by: sortBy,
64-
start: rankingStartFrom,
65-
limit: LIMIT,
60+
useEffect(() => {
61+
let isCancelled = false;
62+
63+
const fetchData = async () => {
64+
const { data, error } = await client.GET("/scores/ranking", {
65+
params: {
66+
query: {
67+
sort_by: sortBy,
68+
start: rankingStartFrom,
69+
limit: LIMIT,
70+
},
6671
},
67-
},
68-
});
69-
if (data) {
70-
setScoreRankings(data.rankings);
71-
setTotalRankingCount(data.total_count);
72-
} else {
73-
showErrorToast(error);
74-
}
75-
}, [sortBy, rankingStartFrom]);
72+
});
7673

77-
useEffect(() => {
78-
fetchData();
79-
}, [fetchData]);
74+
if (isCancelled) {
75+
return;
76+
}
77+
78+
if (data) {
79+
setScoreRankings(data.rankings);
80+
setTotalRankingCount(data.total_count);
81+
} else {
82+
showErrorToast(error);
83+
}
84+
};
85+
86+
void fetchData();
87+
88+
return () => {
89+
isCancelled = true;
90+
};
91+
}, [refreshKey, sortBy, rankingStartFrom]);
8092

8193
const handleTabChange = (index: number) => {
8294
const sortOption = index === 0 ? "accuracy" : "keystrokes";
@@ -118,7 +130,7 @@ const RankingTabs = () => {
118130
<RefreshButton
119131
onClick={() => {
120132
setRankingStartFrom(1);
121-
fetchData();
133+
setRefreshKey((prev) => prev + 1);
122134
}}
123135
/>
124136
</div>

typing-app/src/components/templates/GameTyping.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const GameTyping: React.FC<GameTypingProps> = ({ nextPage, subjectText, setScore
3131
});
3232

3333
// 開始時刻と処理フラグの参照
34-
const startTimeRef = useRef<number>(Date.now());
34+
const startTimeRef = useRef<number>(0);
3535
const isProcessingRef = useRef(false);
3636

3737
const typingQueueRef = useRef<number[]>([]);

0 commit comments

Comments
 (0)