Initially developed with WordPress, I decided to refactor the whole project with Next.js.
To retrieve all projects (portfolio) and blog posts, I've used the WordPress API with GraphQL.
Since the project part has ACF custom fields. I use a second GraphQL extension to manage them
TypeScript: v5.9+Next.js: v16.0 (App Router)React: v19.2Node.js: v24+ (Volta)pnpm: v10.3 (Package manager)
Tailwind CSS: v4.1+ with Typography pluginFramer Motion: v12+ for page transitions and animationsGSAP: v3+ for advanced animations
WordPress: v6.7+ (Headless CMS)GraphQL: WPGraphQL for WordPress API communicationWPGraphQL ACF: For Advanced Custom Fields supportSWR: Client-side data fetching
MDX: Blog posts with frontmatter supportReact Hook Form: Form managementAkismet: Spam protection for contact formMailjet: Email service integration
Sentry: Error tracking and performance monitoringTurbopack: Build tool (dev & production)Jest: Testing frameworkESLint: Next.js core + TypeScript + Prettier
Make sure to use a recent version of Node.js (>= v24).
pnpm install
pnpm devYou can now access to the project with: http://localhost:3000
pnpm dev- Start development server with Turbopack (http://localhost:3000)pnpm build- Build production application with Turbopackpnpm start- Start production serverpnpm lint- Run ESLint for code qualitypnpm type-check- TypeScript type checkingpnpm test- Run Jest tests in watch mode
To correctly run this project, you must create an environment variable named .env.local.
AKISMET_API_KEY: Your Akismet API Key to check spamMJ_APIKEY_PUBLIC: Your API Mailjet usernameMJ_APIKEY_PRIVATE: Your API Mailjet passwordWORDPRESS_API_URL: https://YOUR-WEBSITE/graphqlWORDPRESS_AUTH_REFRESH_TOKEN: If you need to access to your private and unpublished contentWORDPRESS_PREVIEW_SECRET: The token used by/api/preview?secret=XXXSLACK_WEBHOOK_URL: If set, on each contact message, a Slack Webhook will be sent.
app/
βββ (pages)/ # Route groups for main pages
βββ components/ # Reusable React components
βββ types/ # TypeScript type definitions (including GraphQL types)
βββ libs/ # Utility libraries and API functions
βββ utils/ # Helper utilities
βββ hooks/ # Custom React hooks
βββ layouts/ # Layout components
βββ graphql/ # GraphQL queries and mutations
The project uses TypeScript path aliases for cleaner imports:
@/*βapp/*@component/*βapp/components/*@layout/*βapp/layouts/*@hook/*βapp/hooks/*@type/*βapp/types/*@lib/*βapp/libs/*@util/*βapp/utils/*@image/*βpublic/images/*@graphql-query/*βapp/graphql/*
- App Router: Using Next.js 16 App Router architecture
- TypeScript Strict Mode: Comprehensive type safety
- MDX Support: Blog posts written in MDX with frontmatter
- Image Optimization: Remote WordPress images served via i0.wp.com
- Preview Mode: Draft content preview functionality
- Tailwind Custom Theme: 8px increment spacing system with custom color palette
- Authentication: JWT-based for accessing private WordPress content
in this part, we will configure the WordPress part to ensure the communication with Next.js
Once the site is ready, you'll need to install the WPGraphQL plugin. It will add GraphQL API to your WordPress site, which we'll use to query the posts. Follow these steps to install it:
Download the WPGraphQL repo as a ZIP archive.
First thing first, we'll create a JWT constant in our wp-config.php.
define('GRAPHQL_JWT_AUTH_SECRET_KEY', 'XXXXXXX');It's recommended that you use something like the WordPress Salt generator (https://api.wordpress.org/secret-key/1.1/salt/) to generate a Secret.
You can install and activate the plugin like any WordPress plugin. Download the .zip from GitHub Release page of WPGraphql JWT Authentication and add to your plugins directory, then activate.
Once installed, in the GraphQL IDE, run the following mutation :
mutation Login {
login(
input: {
clientMutationId: "uniqueId"
password: "your_password"
username: "your_username"
}
) {
refreshToken
}
}Copy the refreshToken returned by the mutation, then open .env.local, and make the following changes:
WORDPRESS_AUTH_REFRESH_TOKEN: set it to be therefreshTokenyou just received.