Next.js 16 (#347)

* Upgrade to Next.js 16, resolve/suppress linting errors

* Update usage of revalidateTag()

* Rename proxy.ts export

* Refactor infinite scroll data handling
This commit is contained in:
Sam Becker 2025-10-25 21:35:30 -05:00 committed by GitHub
parent e56b386a20
commit 5591635a1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1880 additions and 1955 deletions

View File

@ -1,27 +1,26 @@
import { dirname } from 'path'; import { defineConfig, globalIgnores } from 'eslint/config';
import { fileURLToPath } from 'url'; import nextVitals from 'eslint-config-next/core-web-vitals';
import { FlatCompat } from '@eslint/eslintrc'; import nextTs from 'eslint-config-next/typescript';
import stylistic from '@stylistic/eslint-plugin'; import stylistic from '@stylistic/eslint-plugin';
const __filename = fileURLToPath(import.meta.url); const eslintConfig = defineConfig([
const __dirname = dirname(__filename); ...nextVitals,
...nextTs,
const compat = new FlatCompat({ // Override default ignores of eslint-config-next.
baseDirectory: __dirname, globalIgnores([
}); // Default ignores of eslint-config-next:
'.next/**',
const eslintConfig = [{ 'out/**',
ignores: [ 'build/**',
'.*',
'node_modules',
'next-env.d.ts', 'next-env.d.ts',
], ]), {
},
...compat.extends('next/core-web-vitals', 'next/typescript'), {
plugins: { plugins: {
'@stylistic': stylistic, '@stylistic': stylistic,
}, },
rules: { rules: {
// Temporarily disable during Next.js 16 migration
'react-hooks/refs': 'off',
'react-hooks/set-state-in-effect': 'off',
'@next/next/no-img-element': 'off', '@next/next/no-img-element': 'off',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-require-imports': 'off',
@ -55,6 +54,8 @@ const eslintConfig = [{
{ 'code': 80 }, { 'code': 80 },
], ],
}, },
}]; },
]);
export default eslintConfig; export default eslintConfig;

View File

@ -1,30 +1,30 @@
{ {
"name": "exif-photo-blog", "name": "exif-photo-blog",
"scripts": { "scripts": {
"dev": "next dev --turbo", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "eslint .", "lint": "eslint .",
"test": "jest --watch --transformIgnorePatterns 'node_modules/(?!my-library-dir)/'", "test": "jest --watch --transformIgnorePatterns 'node_modules/(?!my-library-dir)/'",
"analyze": "ANALYZE=true next build" "analyze": "ANALYZE=true next build"
}, },
"packageManager": "pnpm@10.18.2", "packageManager": "pnpm@10.19.0",
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^2.0.40", "@ai-sdk/openai": "^2.0.53",
"@ai-sdk/rsc": "^1.0.59", "@ai-sdk/rsc": "^1.0.79",
"@aws-sdk/client-s3": "3.899.0", "@aws-sdk/client-s3": "3.917.0",
"@aws-sdk/s3-request-presigner": "3.899.0", "@aws-sdk/s3-request-presigner": "3.917.0",
"@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@radix-ui/react-visually-hidden": "^1.2.3", "@radix-ui/react-visually-hidden": "^1.2.3",
"@types/piexifjs": "^1.0.0", "@types/piexifjs": "^1.0.0",
"@upstash/ratelimit": "^2.0.6", "@upstash/ratelimit": "^2.0.6",
"@upstash/redis": "^1.35.4", "@upstash/redis": "^1.35.6",
"@vercel/analytics": "^1.5.0", "@vercel/analytics": "^1.5.0",
"@vercel/blob": "^2.0.0", "@vercel/blob": "^2.0.0",
"@vercel/speed-insights": "^1.2.0", "@vercel/speed-insights": "^1.2.0",
"ai": "^5.0.59", "ai": "^5.0.79",
"camelcase-keys": "^10.0.0", "camelcase-keys": "^10.0.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
@ -35,16 +35,16 @@
"extract-colors": "^4.2.1", "extract-colors": "^4.2.1",
"fast-average-color": "^9.5.0", "fast-average-color": "^9.5.0",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"framer-motion": "^12.23.22", "framer-motion": "^12.23.24",
"nanoid": "^5.1.6", "nanoid": "^5.1.6",
"next": "15.5.4", "next": "16.0.0",
"next-auth": "5.0.0-beta.29", "next-auth": "5.0.0-beta.29",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"ol": "^10.6.1", "ol": "^10.6.1",
"pg": "^8.16.3", "pg": "^8.16.3",
"piexifjs": "^1.0.6", "piexifjs": "^1.0.6",
"react": "19.1.1", "react": "19.2.0",
"react-dom": "19.1.1", "react-dom": "19.2.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-openlayers": "^10.5.1", "react-openlayers": "^10.5.1",
"sanitize-html": "^2.17.0", "sanitize-html": "^2.17.0",
@ -54,34 +54,34 @@
"ts-exif-parser": "^0.2.2", "ts-exif-parser": "^0.2.2",
"use-debounce": "^10.0.6", "use-debounce": "^10.0.6",
"viewerjs": "^1.11.7", "viewerjs": "^1.11.7",
"zod": "^4.1.11" "zod": "^4.1.12"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@next/bundle-analyzer": "15.5.4", "@next/bundle-analyzer": "16.0.0",
"@next/eslint-plugin-next": "15.5.4", "@next/eslint-plugin-next": "16.0.0",
"@stylistic/eslint-plugin": "^5.4.0", "@stylistic/eslint-plugin": "^5.5.0",
"@tailwindcss/postcss": "^4.1.13", "@tailwindcss/postcss": "^4.1.16",
"@testing-library/dom": "^10.4.1", "@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.8.0", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@types/culori": "^4.0.1", "@types/culori": "^4.0.1",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/node": "^24.6.0", "@types/node": "^24.9.1",
"@types/pg": "^8.15.5", "@types/pg": "^8.15.5",
"@types/react": "19.1.15", "@types/react": "19.2.2",
"@types/react-dom": "19.1.9", "@types/react-dom": "19.2.2",
"@types/sanitize-html": "^2.16.0", "@types/sanitize-html": "^2.16.0",
"cross-fetch": "^4.1.0", "cross-fetch": "^4.1.0",
"eslint": "9.36.0", "eslint": "9.38.0",
"eslint-config-next": "15.5.4", "eslint-config-next": "16.0.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^7.0.1",
"jest": "^30.2.0", "jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0", "jest-environment-jsdom": "^30.2.0",
"postcss": "8.5.6", "postcss": "8.5.6",
"tailwindcss": "4.1.13", "tailwindcss": "4.1.16",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "5.9.2" "typescript": "5.9.3"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [

3508
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ import {
PREFIX_TAG, PREFIX_TAG,
} from './src/app/path'; } from './src/app/path';
export default function middleware(req: NextRequest, res:NextResponse) { export function proxy(req: NextRequest, res:NextResponse) {
const pathname = req.nextUrl.pathname; const pathname = req.nextUrl.pathname;
if (pathname === PATH_ADMIN) { if (pathname === PATH_ADMIN) {

View File

@ -25,8 +25,9 @@ export default function AdminPhotosTableInfinite({
sortBy="createdAt" sortBy="createdAt"
includeHiddenPhotos includeHiddenPhotos
> >
{({ photos, onLastPhotoVisible, revalidatePhoto }) => {({ key, photos, onLastPhotoVisible, revalidatePhoto }) =>
<AdminPhotosTable <AdminPhotosTable
key={key}
photos={photos} photos={photos}
onLastPhotoVisible={onLastPhotoVisible} onLastPhotoVisible={onLastPhotoVisible}
revalidatePhoto={revalidatePhoto} revalidatePhoto={revalidatePhoto}

View File

@ -62,12 +62,12 @@ export default function AppStateProvider({
useState(false); useState(false);
const [nextPhotoAnimation, _setNextPhotoAnimation] = const [nextPhotoAnimation, _setNextPhotoAnimation] =
useState<AnimationConfig>(); useState<AnimationConfig>();
const [nextPhotoAnimationId, setNextPhotoAnimationId] =
useState<string>();
const setNextPhotoAnimation = useCallback((animation?: AnimationConfig) => { const setNextPhotoAnimation = useCallback((animation?: AnimationConfig) => {
_setNextPhotoAnimation(animation); _setNextPhotoAnimation(animation);
setNextPhotoAnimationId(undefined); setNextPhotoAnimationId(undefined);
}, []); }, []);
const [nextPhotoAnimationId, setNextPhotoAnimationId] =
useState<string>();
const getNextPhotoAnimationId = useCallback(() => { const getNextPhotoAnimationId = useCallback(() => {
const id = nanoid(); const id = nanoid();
setNextPhotoAnimationId(id); setNextPhotoAnimationId(id);

View File

@ -83,6 +83,8 @@ export default function AppViewSwitcher({
const refHrefFull = useRef<HTMLAnchorElement>(null); const refHrefFull = useRef<HTMLAnchorElement>(null);
const refHrefGrid = useRef<HTMLAnchorElement>(null); const refHrefGrid = useRef<HTMLAnchorElement>(null);
const [isAdminMenuOpen, setIsAdminMenuOpen] = useState(false);
const onKeyDown = useCallback((e: KeyboardEvent) => { const onKeyDown = useCallback((e: KeyboardEvent) => {
if (!e.metaKey) { if (!e.metaKey) {
switch (e.key.toLocaleUpperCase()) { switch (e.key.toLocaleUpperCase()) {
@ -101,7 +103,6 @@ export default function AppViewSwitcher({
useKeydownHandler({ onKeyDown }); useKeydownHandler({ onKeyDown });
const [isSortMenuOpen, setIsSortMenuOpen] = useState(false); const [isSortMenuOpen, setIsSortMenuOpen] = useState(false);
const [isAdminMenuOpen, setIsAdminMenuOpen] = useState(false);
const renderItemFull = const renderItemFull =
<SwitcherItem <SwitcherItem
@ -135,7 +136,7 @@ export default function AppViewSwitcher({
className={clsx( className={clsx(
GAP_CLASS_RIGHT, GAP_CLASS_RIGHT,
// Apply offset due to outline strategy // Apply offset due to outline strategy
'translate-x-[1px]', 'translate-x-px',
)} )}
> >
{GRID_HOMEPAGE_ENABLED ? renderItemGrid : renderItemFull} {GRID_HOMEPAGE_ENABLED ? renderItemGrid : renderItemFull}
@ -209,7 +210,7 @@ export default function AppViewSwitcher({
href={pathSortToggle} href={pathSortToggle}
icon={<IconSort icon={<IconSort
sort={isAscending ? 'asc' : 'desc'} sort={isAscending ? 'asc' : 'desc'}
className="translate-x-[0.5px] translate-y-[1px]" className="translate-x-[0.5px] translate-y-px"
/>} />}
tooltip={{...SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && { tooltip={{...SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && {
content: isAscending content: isAscending

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import { ReactNode, useRef } from 'react'; import { ReactNode, useRef } from 'react';
import { Variant, motion } from 'framer-motion'; import { Variant, motion, stagger } from 'framer-motion';
import { useAppState } from '@/app/AppState'; import { useAppState } from '@/app/AppState';
import usePrefersReducedMotion from '@/utility/usePrefersReducedMotion'; import usePrefersReducedMotion from '@/utility/usePrefersReducedMotion';
@ -73,7 +73,8 @@ function AnimateItems({
? (nextPhotoAnimationInitial.current?.duration ?? duration) ? (nextPhotoAnimationInitial.current?.duration ?? duration)
: duration; : duration;
const getInitialVariant = (): Variant => { const hidden: Variant =
(() => {
switch (typeResolved) { switch (typeResolved) {
case 'left': return { case 'left': return {
opacity: 0, opacity: 0,
@ -91,8 +92,7 @@ function AnimateItems({
opacity: 0, opacity: 0,
transform: `translateY(${distanceOffset}px) scale(${scaleOffset})`, transform: `translateY(${distanceOffset}px) scale(${scaleOffset})`,
}; };
} }})();
};
return ( return (
<motion.div <motion.div
@ -103,7 +103,7 @@ function AnimateItems({
? { ? {
show: { show: {
transition: { transition: {
staggerChildren: staggerDelay, delayChildren: stagger(staggerDelay),
}, },
}, },
} : undefined} } : undefined}
@ -122,7 +122,7 @@ function AnimateItems({
key={itemKeys ? itemKeys[index] : index} key={itemKeys ? itemKeys[index] : index}
className={classNameItem} className={classNameItem}
variants={{ variants={{
hidden: getInitialVariant(), hidden,
show: { show: {
opacity: 1, opacity: 1,
transform: 'translateX(0) translateY(0) scale(1)', transform: 'translateX(0) translateY(0) scale(1)',

View File

@ -59,11 +59,11 @@ export default function useMaskedScroll({
useEffect(() => { useEffect(() => {
const ref = containerRef?.current; const ref = containerRef?.current;
if (ref && updateMaskOnEvents) { if (ref && updateMaskOnEvents) {
ref.onscroll = updateMask; ref.addEventListener('scroll', updateMask);
ref.onresize = updateMask; ref.addEventListener('resize', updateMask);
return () => { return () => {
ref.onscroll = null; ref.removeEventListener('scroll', updateMask);
ref.onresize = null; ref.removeEventListener('resize', updateMask);
}; };
} }
}, [containerRef, updateMask, updateMaskOnEvents]); }, [containerRef, updateMask, updateMaskOnEvents]);

View File

@ -58,6 +58,7 @@ export default function InfinitePhotoScroll({
useCachedPhotos?: boolean useCachedPhotos?: boolean
includeHiddenPhotos?: boolean includeHiddenPhotos?: boolean
children: (props: { children: (props: {
key: string
photos: Photo[] photos: Photo[]
onLastPhotoVisible: () => void onLastPhotoVisible: () => void
revalidatePhoto?: RevalidatePhoto revalidatePhoto?: RevalidatePhoto
@ -140,8 +141,6 @@ export default function InfinitePhotoScroll({
} }
}, [isFinished, isLoadingOrValidating, setSize]); }, [isFinished, isLoadingOrValidating, setSize]);
const photos = useMemo(() => (data ?? [])?.flat(), [data]);
const revalidatePhoto: RevalidatePhoto = useCallback(( const revalidatePhoto: RevalidatePhoto = useCallback((
photoId: string, photoId: string,
revalidateRemainingPhotos?: boolean, revalidateRemainingPhotos?: boolean,
@ -159,7 +158,7 @@ export default function InfinitePhotoScroll({
} }
}}); }});
const renderMoreButton = () => const renderMoreButton =
<div ref={buttonContainerRef}> <div ref={buttonContainerRef}>
<button <button
type="button" type="button"
@ -179,15 +178,20 @@ export default function InfinitePhotoScroll({
</div>; </div>;
return ( return (
<div className="space-y-4"> <>
{children({ {data?.map((photos, index) => (
children({
key: `${cacheKey}-${index}`,
photos, photos,
onLastPhotoVisible: advance, onLastPhotoVisible: advance,
revalidatePhoto, revalidatePhoto,
})} })
{!isFinished && (wrapMoreButtonInGrid ))}
? <AppGrid contentMain={renderMoreButton()} /> {!isFinished && <div className="mt-4">
: renderMoreButton())} {wrapMoreButtonInGrid
</div> ? <AppGrid contentMain={renderMoreButton} />
: renderMoreButton}
</div>}
</>
); );
} }

View File

@ -32,8 +32,8 @@ export default function PhotoGridInfinite({
excludeFromFeeds={excludeFromFeeds} excludeFromFeeds={excludeFromFeeds}
{...categories} {...categories}
> >
{({ photos, onLastPhotoVisible }) => {({ key, photos, onLastPhotoVisible }) =>
<PhotoGrid {...{ <PhotoGrid key={key} {...{
photos, photos,
...categories, ...categories,
canStart, canStart,

View File

@ -65,11 +65,11 @@ export default function PhotoPrevNextActions({
const toggleFavorite = useCallback(() => { const toggleFavorite = useCallback(() => {
if (photo?.id) { return toggleFavoritePhotoAction(photo.id); } if (photo?.id) { return toggleFavoritePhotoAction(photo.id); }
}, [photo?.id]); }, [photo]);
const toggleHidden = useCallback(() => { const toggleHidden = useCallback(() => {
if (photo?.id) { return togglePrivatePhotoAction(photo.id); } if (photo?.id) { return togglePrivatePhotoAction(photo.id); }
}, [photo?.id]); }, [photo]);
const navigateToPhotoEdit = useNavigateOrRunActionWithToast({ const navigateToPhotoEdit = useNavigateOrRunActionWithToast({
pathOrAction: photo ? pathForAdminPhotoEdit(photo) : undefined, pathOrAction: photo ? pathForAdminPhotoEdit(photo) : undefined,
@ -99,7 +99,7 @@ export default function PhotoPrevNextActions({
const syncPhoto = useNavigateOrRunActionWithToast({ const syncPhoto = useNavigateOrRunActionWithToast({
pathOrAction: useCallback(() => { pathOrAction: useCallback(() => {
if (photo?.id) { return syncPhotoAction(photo.id); } if (photo?.id) { return syncPhotoAction(photo.id); }
}, [photo?.id]), }, [photo]),
toastMessage: `Syncing ${photoTitle} ...`, toastMessage: `Syncing ${photoTitle} ...`,
}); });
@ -108,7 +108,7 @@ export default function PhotoPrevNextActions({
if (photo?.id && photo.url) { if (photo?.id && photo.url) {
return deletePhotoAction(photo.id, photo.url, true); return deletePhotoAction(photo.id, photo.url, true);
} }
}, [photo?.id, photo?.url]), }, [photo]),
toastMessage: `Deleting ${photoTitle} ...`, toastMessage: `Deleting ${photoTitle} ...`,
}); });

View File

@ -26,8 +26,9 @@ export default function PhotosLargeInfinite({
excludeFromFeeds={excludeFromFeeds} excludeFromFeeds={excludeFromFeeds}
wrapMoreButtonInGrid wrapMoreButtonInGrid
> >
{({ photos, onLastPhotoVisible, revalidatePhoto }) => {({ key, photos, onLastPhotoVisible, revalidatePhoto }) =>
<PhotosLarge <PhotosLarge
key={key}
photos={photos} photos={photos}
onLastPhotoVisible={onLastPhotoVisible} onLastPhotoVisible={onLastPhotoVisible}
revalidatePhoto={revalidatePhoto} revalidatePhoto={revalidatePhoto}

View File

@ -17,8 +17,9 @@ export default function StaggeredOgPhotosInfinite({
initialOffset={initialOffset} initialOffset={initialOffset}
itemsPerPage={itemsPerPage} itemsPerPage={itemsPerPage}
> >
{({ photos, onLastPhotoVisible }) => {({ key, photos, onLastPhotoVisible }) =>
<StaggeredOgPhotos <StaggeredOgPhotos
key={key}
photos={photos} photos={photos}
onLastPhotoVisible={onLastPhotoVisible} onLastPhotoVisible={onLastPhotoVisible}
/>} />}

View File

@ -102,31 +102,31 @@ const getPhotosCacheKeys = (options: PhotoQueryOptions = {}) => {
}; };
export const revalidatePhotosKey = () => export const revalidatePhotosKey = () =>
revalidateTag(KEY_PHOTOS); revalidateTag(KEY_PHOTOS, 'max');
export const revalidateAlbumsKey = () => export const revalidateAlbumsKey = () =>
revalidateTag(KEY_ALBUMS); revalidateTag(KEY_ALBUMS, 'max');
export const revalidateTagsKey = () => export const revalidateTagsKey = () =>
revalidateTag(KEY_TAGS); revalidateTag(KEY_TAGS, 'max');
export const revalidateRecipesKey = () => export const revalidateRecipesKey = () =>
revalidateTag(KEY_RECIPES); revalidateTag(KEY_RECIPES, 'max');
export const revalidateCamerasKey = () => export const revalidateCamerasKey = () =>
revalidateTag(KEY_CAMERAS); revalidateTag(KEY_CAMERAS, 'max');
export const revalidateLensesKey = () => export const revalidateLensesKey = () =>
revalidateTag(KEY_LENSES); revalidateTag(KEY_LENSES, 'max');
export const revalidateFilmsKey = () => export const revalidateFilmsKey = () =>
revalidateTag(KEY_FILMS); revalidateTag(KEY_FILMS, 'max');
export const revalidateFocalLengthsKey = () => export const revalidateFocalLengthsKey = () =>
revalidateTag(KEY_FOCAL_LENGTHS); revalidateTag(KEY_FOCAL_LENGTHS, 'max');
export const revalidateYearsKey = () => export const revalidateYearsKey = () =>
revalidateTag(KEY_YEARS); revalidateTag(KEY_YEARS, 'max');
export const revalidateAllKeys = () => { export const revalidateAllKeys = () => {
revalidatePhotosKey(); revalidatePhotosKey();
@ -151,7 +151,7 @@ export const revalidateAllKeysAndPaths = () => {
export const revalidatePhoto = (photoId: string) => { export const revalidatePhoto = (photoId: string) => {
// Tags // Tags
revalidateTag(photoId); revalidateTag(photoId, 'max');
revalidateYearsKey(); revalidateYearsKey();
revalidateCamerasKey(); revalidateCamerasKey();
revalidateLensesKey(); revalidateLensesKey();

View File

@ -334,7 +334,7 @@ export default function PhotoForm({
? 'true' ? 'true'
: 'false', : 'false',
})); }));
}, []); }, [setFormData]);
const formContent = useMemo(() => const formContent = useMemo(() =>
FORM_METADATA_ENTRIES_BY_SECTION( FORM_METADATA_ENTRIES_BY_SECTION(
@ -371,7 +371,7 @@ export default function PhotoForm({
blurCompatibilityLevel="none" blurCompatibilityLevel="none"
width={thumbnailDimensions.width} width={thumbnailDimensions.width}
height={thumbnailDimensions.height} height={thumbnailDimensions.height}
priority preload
/>; />;
return ( return (

View File

@ -1,7 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2019", "target": "ES2019",
"lib": ["dom", "dom.iterable", "esnext"], "lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@ -11,7 +15,7 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "react-jsx",
"incremental": true, "incremental": true,
"plugins": [ "plugins": [
{ {
@ -19,9 +23,19 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": [
"./src/*"
]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": [
"exclude": ["node_modules"] "next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
} }