Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Ahondev/portfolio-v2/llms.txt

Use this file to discover all available pages before exploring further.

Every URL served by the WP SSR Framework has two sides: a PHP controller method that produces the view name and data, and a React page component that renders it. The connection between them is the routes.ts file, which maps view name strings to lazy-imported React components.

The Route Map (routes.ts)

routes.ts
export const routes = {
  'home':                () => import("./pages/Home"),
  'agence':              () => import("./pages/Agence"),
  'services':            () => import("./pages/ServicesIndex"),
  'services_single':     () => import("./pages/ServiceDetail"),
  'realisations':        () => import("./pages/Realisations"),
  'realisations_single': () => import("./pages/RealisationDetail.tsx"),
  'blog':                () => import("./pages/BlogIndex"),
  'blog_single':         () => import("./pages/BlogPost"),
  'contact':             () => import("./pages/Contact"),
  'devis':               () => import("./pages/Devis"),
}
Each value is a function that returns a dynamic import() — Vite code-splits every page into a separate bundle, so users only download the JavaScript for the pages they actually visit.

View Name Convention

The view name string in routes.ts must exactly match the first argument passed to $this->view() in the PHP controller.
Route typePHP callView name
Static page$this->view('home', $data)home
Static page$this->view('devis')devis
CPT archive$this->view('blog', $data)blog
CPT single$this->postView('blog_single', $post, $data)blog_single
CPT archive$this->view('services', $data)services
CPT single$this->postView('services_single', $post, $data)services_single
There is no automatic convention for CPT single view names — the controller author chooses the name. The pattern {postType}_single is a project-level convention, not enforced by the framework.

Accessing Page Data in React

Use the useWPData<T>() hook from wp-sync.ts to read the data the PHP controller provided. Define a TypeScript type that matches the controller’s $data array.

Static Page Example

PHP controller
public function devis(): string
{
    return $this->view('devis');
    // No data — the view only uses Composer globals
}
React component
// pages/Devis.tsx
import { useWPData } from "@/lib/wp-sync"

// Composer data injected globally by AppComposer
type DevisData = {
  site_email: string
  site_services: Array<{ id: number; title: string; slug: string }>
}

export default function Devis() {
  const { site_email, site_services } = useWPData<DevisData>()
  return <form>...</form>
}

CPT Single Example

PHP controller
public function single(?EloquentCPT $post): string|bool
{
    return $this->postView('blog_single', $post, [
        'posts' => Article::query()->all(),
    ]);
}
React component
// pages/BlogPost.tsx
import { useWPData } from "@/lib/wp-sync"

type BlogPostData = {
  post: {
    title: string
    content: string
    date: string
    slug: string
    excerpt: string
    readTime: string
    category: { name: string }
  }
  posts: Array<{
    id: number
    title: string
    slug: string
    date: string
    readTime: string
    excerpt: string
    category?: { name: string }
  }>
}

export default function BlogPost() {
  const data = useWPData<BlogPostData>()
  const post = data.post
  if (!post) return <div>Article introuvable</div>
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  )
}

Normalize Utilities

ACF field values from WordPress often have inconsistent shapes (arrays of objects, image objects with url/alt, etc.). The project includes normalizer functions to clean up raw data before use:
// Transforms raw ACF service data to a clean typed shape.
// Spreads all raw fields and overrides the ones that need reshaping.
// Usage: const service = normalizeService(useWPData().post)
export function normalizeService(raw: any): ServiceContent {
  return {
    ...raw,
    hero: {
      title: raw['hero.title'] || '',
      subtitle: raw['hero.subtitle'] || '',
      accent: raw['hero.accent'] || '',
    },
    highlights: (raw.highlights || []).map((h: any) => h.value),
    useCases:   (raw.useCases   || []).map((u: any) => u.value),
    technologies: (raw.technologies || []).map((t: any) => t.value),
    features: raw.features || [],
    whyAhon:  raw.whyAhon  || [],
    process:  raw.process  || [],
    faq:      raw.faq      || [],
  }
}

Adding a New View End-to-End

1

Create the PHP controller method

Add a method to an existing controller (or create a new one extending WebController):
app/Controllers/Web/HomeController.php
public function portfolio(): string
{
    $projects = Realisation::query()->all();
    return $this->view('portfolio', [
        'projects' => $projects,
    ]);
}
2

Register the route

Add the route to configuration/routes/web.php:
configuration/routes/web.php
Route::get('/portfolio', 'Portfolio', [HomeController::class, 'portfolio']);
In development, WordPress will auto-create the /portfolio page. In production, run wp pages:sync.
3

Create the React component

Create web/client/src/pages/Portfolio.tsx:
pages/Portfolio.tsx
import { useWPData } from "@/lib/wp-sync"

type PortfolioData = {
  projects: Array<{
    id: number
    title: string
    slug: string
    type: string
    year: string
  }>
}

export default function Portfolio() {
  const { projects } = useWPData<PortfolioData>()
  return (
    <section>
      <h1>Portfolio</h1>
      {projects.map(p => (
        <a key={p.id} href={`/realisations/${p.slug}`}>{p.title}</a>
      ))}
    </section>
  )
}
4

Register in routes.ts

Add the lazy import:
routes.ts
export const routes = {
  // ...existing routes
  'portfolio': () => import("./pages/Portfolio"),
}
5

Rebuild the frontend

cd web/client
yarn build
During development with yarn dev, Vite’s HMR updates the browser instantly whenever you save a React component. You don’t need to rebuild after each change.

Build docs developers (and LLMs) love