Skip to content

Commit 0a3294a

Browse files
authored
feat: upgrade to Next.js 16 and improve code quality tools (#172)
2 parents 1d66f23 + 9b8e23d commit 0a3294a

File tree

14 files changed

+5315
-1439
lines changed

14 files changed

+5315
-1439
lines changed

.husky/commit-msg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
npx commitlint --edit $1

.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pnpm exec lint-staged

README.md

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,44 @@ Since the project part has ACF custom fields. I use a second [GraphQL extension]
1818

1919
## 📦 Stack
2020

21-
- `TypeScript`: v5
22-
- `NextJS`: v15.x
23-
- `Node`: v22+
24-
- `Tailwind CSS`: v4
25-
- `WordPress`: v6.7+
26-
- `Framer Motion`: for animations between page transitions
27-
- `Akismet`: to check spam in the contact form
28-
- `GraphQL`: communication with the WordPress API
21+
### Core Technologies
22+
23+
- `TypeScript`: v5.9+
24+
- `Next.js`: v16.0 (App Router)
25+
- `React`: v19.2
26+
- `Node.js`: v24+ (Volta)
27+
- `pnpm`: v10.3 (Package manager)
28+
29+
### Styling & Animations
30+
31+
- `Tailwind CSS`: v4.1+ with Typography plugin
32+
- `Framer Motion`: v12+ for page transitions and animations
33+
- `GSAP`: v3+ for advanced animations
34+
35+
### Backend & Data
36+
37+
- `WordPress`: v6.7+ (Headless CMS)
38+
- `GraphQL`: WPGraphQL for WordPress API communication
39+
- `WPGraphQL ACF`: For Advanced Custom Fields support
40+
- `SWR`: Client-side data fetching
41+
42+
### Content & Forms
43+
44+
- `MDX`: Blog posts with frontmatter support
45+
- `React Hook Form`: Form management
46+
- `Akismet`: Spam protection for contact form
47+
- `Mailjet`: Email service integration
48+
49+
### DevOps & Monitoring
50+
51+
- `Sentry`: Error tracking and performance monitoring
52+
- `Turbopack`: Build tool (dev & production)
53+
- `Jest`: Testing framework
54+
- `ESLint`: Next.js core + TypeScript + Prettier
2955

3056
## ⚡️ Installation
3157

32-
Make sure to use a recent version of Node.js (>= v22).
58+
Make sure to use a recent version of Node.js (>= v24).
3359

3460
```bash
3561
pnpm install
@@ -38,6 +64,15 @@ pnpm dev
3864

3965
You can now access to the project with: http://localhost:3000
4066

67+
### Available Commands
68+
69+
- `pnpm dev` - Start development server with Turbopack (http://localhost:3000)
70+
- `pnpm build` - Build production application with Turbopack
71+
- `pnpm start` - Start production server
72+
- `pnpm lint` - Run ESLint for code quality
73+
- `pnpm type-check` - TypeScript type checking
74+
- `pnpm test` - Run Jest tests in watch mode
75+
4176
## 🔧 Configuration
4277

4378
To correctly run this project, you must create an environment variable named `.env.local`.
@@ -50,6 +85,46 @@ To correctly run this project, you must create an environment variable named `.e
5085
- `WORDPRESS_PREVIEW_SECRET`: The token used by `/api/preview?secret=XXX`
5186
- `SLACK_WEBHOOK_URL`: If set, on each contact message, a Slack Webhook will be sent.
5287

88+
## 🏗️ Architecture
89+
90+
### Project Structure
91+
92+
```
93+
app/
94+
├── (pages)/ # Route groups for main pages
95+
├── components/ # Reusable React components
96+
├── types/ # TypeScript type definitions (including GraphQL types)
97+
├── libs/ # Utility libraries and API functions
98+
├── utils/ # Helper utilities
99+
├── hooks/ # Custom React hooks
100+
├── layouts/ # Layout components
101+
└── graphql/ # GraphQL queries and mutations
102+
```
103+
104+
### Path Aliases
105+
106+
The project uses TypeScript path aliases for cleaner imports:
107+
108+
- `@/*``app/*`
109+
- `@component/*``app/components/*`
110+
- `@layout/*``app/layouts/*`
111+
- `@hook/*``app/hooks/*`
112+
- `@type/*``app/types/*`
113+
- `@lib/*``app/libs/*`
114+
- `@util/*``app/utils/*`
115+
- `@image/*``public/images/*`
116+
- `@graphql-query/*``app/graphql/*`
117+
118+
### Key Features
119+
120+
- **App Router**: Using Next.js 16 App Router architecture
121+
- **TypeScript Strict Mode**: Comprehensive type safety
122+
- **MDX Support**: Blog posts written in MDX with frontmatter
123+
- **Image Optimization**: Remote WordPress images served via i0.wp.com
124+
- **Preview Mode**: Draft content preview functionality
125+
- **Tailwind Custom Theme**: 8px increment spacing system with custom color palette
126+
- **Authentication**: JWT-based for accessing private WordPress content
127+
53128
# 🔒️ WordPress Configuration
54129

55130
in this part, we will configure the WordPress part to ensure the communication with Next.js

app/(pages)/blog/[slug]/page.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,27 @@ export async function generateStaticParams() {
3131

3232
const getData = async (slug: string) => await getSingleBlogItem(slug)
3333

34+
const hashString = (str: string): number => {
35+
let hash = 0
36+
for (let i = 0; i < str.length; i++) {
37+
const char = str.charCodeAt(i)
38+
hash = (hash << 5) - hash + char
39+
hash = hash & hash
40+
}
41+
return Math.abs(hash)
42+
}
43+
44+
const seededShuffle = <T,>(array: T[], seed: string): T[] => {
45+
const hash = hashString(seed)
46+
return array
47+
.map((value, index) => ({
48+
value,
49+
sort: Math.sin(hash + index) * 10000,
50+
}))
51+
.sort((a, b) => a.sort - b.sort)
52+
.map(({ value }) => value)
53+
}
54+
3455
export async function generateMetadata(props: Props) {
3556
const params = await props.params
3657
const { post } = await getData(params.slug)
@@ -56,11 +77,7 @@ export default async function Page(props: Props) {
5677
return notFound()
5778
}
5879

59-
const shuffled = posts
60-
.map((value) => ({ value, sort: Math.random() }))
61-
.sort((a, b) => a.sort - b.sort)
62-
.map(({ value }) => value)
63-
80+
const shuffled = seededShuffle(posts, params.slug)
6481
const relatedPosts = shuffled.slice(0, 2)
6582

6683
return (

app/(pages)/layout.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,25 @@ export async function generateMetadata() {
3131

3232
export default function RootLayout({ children }: { children: ReactNode }) {
3333
const jsonLd = {
34-
organizationType: 'Corporation',
35-
id: 'https://www.inrage.fr/#corporation',
36-
logo: 'https://i1.wp.com/www.inrage.fr/wp-content/uploads/2019/12/logo-inrage.png?fit=150%2C56&ssl=1',
34+
'@context': 'https://schema.org',
35+
'@type': 'Corporation',
36+
'@id': 'https://www.inrage.fr/#corporation',
37+
logo: 'https://static.inrage.fr/signature/logo-inrage-square200.png',
3738
legalName: 'inRage SARL',
3839
name: 'inRage',
3940
address: {
41+
'@type': 'PostalAddress',
4042
streetAddress: '10 rue Jean Perrin',
4143
addressLocality: 'La Rochelle',
4244
addressRegion: 'Charente Maritime',
4345
postalCode: '17000',
4446
addressCountry: 'FR',
4547
},
46-
contactPoints: [
48+
contactPoint: [
4749
{
50+
'@type': 'ContactPoint',
4851
telephone: '+33 (0)6 82 96 38 39',
49-
contactType: 'Pascal GAULT',
52+
contactType: 'customer service',
5053
areaServed: 'FR',
5154
availableLanguage: 'French',
5255
},

app/components/ContactForm.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
'use client'
22

3-
import React from 'react'
4-
53
import { useForm } from 'react-hook-form'
64
import { sendGTMEvent } from '@next/third-parties/google'
75
import * as Sentry from '@sentry/nextjs'

app/components/Layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import React, { Fragment, ReactNode } from 'react'
3+
import { Fragment, ReactNode } from 'react'
44

55
import { motion } from 'motion/react'
66

app/components/NoScrollLink.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { ReactNode } from 'react'
1+
import { ReactNode } from 'react'
22

33
import Link, { LinkProps } from 'next/link'
44

commitlint.config.mjs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export default {
2+
extends: ['@commitlint/config-conventional'],
3+
rules: {
4+
'type-enum': [
5+
2,
6+
'always',
7+
[
8+
'feat',
9+
'fix',
10+
'docs',
11+
'style',
12+
'refactor',
13+
'perf',
14+
'test',
15+
'build',
16+
'ci',
17+
'chore',
18+
'revert',
19+
],
20+
],
21+
'subject-case': [2, 'never', ['upper-case']],
22+
},
23+
};

eslint.config.mjs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
2+
import nextTypescript from "eslint-config-next/typescript";
13
import { dirname } from 'path'
24
import { fileURLToPath } from 'url'
35
import { FlatCompat } from '@eslint/eslintrc'
@@ -9,17 +11,14 @@ const compat = new FlatCompat({
911
baseDirectory: __dirname,
1012
})
1113

12-
const eslintConfig = [
13-
...compat.extends('next/core-web-vitals', 'next/typescript', 'prettier'),
14-
{
15-
ignores: [
16-
'node_modules/**',
17-
'.next/**',
18-
'out/**',
19-
'build/**',
20-
'next-env.d.ts',
21-
],
22-
},
23-
]
14+
const eslintConfig = [...nextCoreWebVitals, ...nextTypescript, ...compat.extends("prettier"), {
15+
ignores: [
16+
'node_modules/**',
17+
'.next/**',
18+
'out/**',
19+
'build/**',
20+
'next-env.d.ts',
21+
],
22+
}]
2423

2524
export default eslintConfig

0 commit comments

Comments
 (0)