Project Structure
Understand the FlatWP Starter template structure and file organization.
Overview
The FlatWP Starter is organized for clarity and scalability:
flatwp-starter/
├── app/ # Next.js App Router
│ ├── (pages)/ # Page routes
│ │ └── [slug]/ # Dynamic pages from WordPress
│ ├── blog/ # Blog routes
│ │ ├── [slug]/ # Individual posts
│ │ ├── category/ # Category archives
│ │ └── tag/ # Tag archives
│ ├── api/ # API routes
│ │ ├── revalidate/ # ISR revalidation endpoint
│ │ └── preview/ # Preview mode handler
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Homepage
│ └── globals.css # Global styles
├── components/ # React components
│ ├── blocks/ # WordPress block components
│ │ ├── core/ # Core Gutenberg blocks
│ │ └── flatwp/ # FlatWP custom blocks
│ ├── blog/ # Blog-specific components
│ ├── layout/ # Header, Footer, Navigation
│ └── ui/ # Shadcn/ui components
├── lib/ # Utilities and helpers
│ ├── wordpress/ # WordPress client & adapters
│ ├── blocks/ # Block registry & utilities
│ └── utils/ # General utilities
├── graphql/ # GraphQL configuration
│ ├── queries/ # GraphQL query files
│ ├── fragments/ # Reusable GraphQL fragments
│ └── generated/ # Auto-generated types
├── public/ # Static assets
├── scripts/ # Build and setup scripts
├── .env.example # Environment template
├── next.config.ts # Next.js configuration
├── tailwind.config.ts # Tailwind CSS configuration
├── codegen.ts # GraphQL Code Generator config
└── package.json # Dependencies and scripts
Directory Details
/app - Next.js App Router
The app directory uses Next.js 14+ App Router conventions.
Route Groups
app/
├── (pages)/ # Grouping for WordPress pages
│ └── [slug]/ # Dynamic route for all pages
│ └── page.tsx
├── (marketing)/ # Marketing pages (optional)
│ ├── about/
│ ├── pricing/
│ └── contact/
└── blog/ # Blog section
├── page.tsx # Blog index
└── [slug]/ # Individual posts
Key Files
| File | Purpose |
|---|---|
layout.tsx | Root layout with header/footer |
page.tsx | Homepage component |
not-found.tsx | 404 error page |
error.tsx | Error boundary |
loading.tsx | Loading skeleton |
/app/api - API Routes
Server-side API endpoints for WordPress integration:
// app/api/revalidate/route.ts
export async function POST(request: Request) {
const { secret, paths } = await request.json();
if (secret !== process.env.FLATWP_SECRET) {
return Response.json({ error: 'Invalid secret' }, { status: 401 });
}
// Revalidate paths
for (const path of paths) {
revalidatePath(path);
}
return Response.json({ revalidated: true, paths });
}
/components - React Components
Organized by feature and purpose:
components/
├── blocks/ # WordPress block renderers
│ ├── core/ # Core Gutenberg blocks
│ │ ├── ParagraphBlock.tsx
│ │ ├── HeadingBlock.tsx
│ │ ├── ImageBlock.tsx
│ │ └── ...
│ ├── flatwp/ # FlatWP custom blocks
│ │ ├── HeroBlock.tsx
│ │ ├── FeaturesBlock.tsx
│ │ ├── CTABlock.tsx
│ │ └── ...
│ └── EditorBlockRenderer.tsx # Main block renderer
├── blog/ # Blog components
│ ├── PostCard.tsx
│ ├── PostGrid.tsx
│ ├── CategoryList.tsx
│ └── Sidebar.tsx
├── layout/ # Layout components
│ ├── Header.tsx
│ ├── Footer.tsx
│ ├── Navigation.tsx
│ └── MobileMenu.tsx
└── ui/ # Shadcn/ui components
├── button.tsx
├── card.tsx
└── ...
/lib - Utilities & Libraries
Core functionality and helpers:
lib/
├── wordpress/ # WordPress integration
│ ├── client.ts # GraphQL client
│ ├── queries.ts # Query helpers
│ └── adapters/ # Data transformers
│ ├── pages.ts
│ ├── posts.ts
│ └── menus.ts
├── blocks/ # Block system
│ ├── registry.ts # Block component registry
│ └── utils.ts # Block utilities
└── utils/ # General utilities
├── cn.ts # Class name helper
└── formatting.ts # Date/text formatting
WordPress Client
// lib/wordpress/client.ts
import { GraphQLClient } from 'graphql-request';
const endpoint = process.env.NEXT_PUBLIC_WORDPRESS_API_URL!;
export const client = new GraphQLClient(endpoint, {
headers: {
// Add auth headers if needed
},
});
export async function fetchGraphQL<T>(
query: string,
variables?: Record<string, unknown>
): Promise<T> {
return client.request<T>(query, variables);
}
/graphql - GraphQL Configuration
GraphQL queries, fragments, and generated types:
graphql/
├── queries/ # Query files
│ ├── pages.graphql
│ ├── posts.graphql
│ └── menus.graphql
├── fragments/ # Reusable fragments
│ ├── post-fields.graphql
│ └── image-fields.graphql
└── generated/ # Auto-generated (don't edit)
├── graphql.ts # Types and operations
└── schema.json # Full schema
Example Query
# graphql/queries/pages.graphql
query GetPageBySlug($slug: ID!) {
page(id: $slug, idType: URI) {
id
title
slug
content
editorBlocks {
...EditorBlockFields
}
featuredImage {
node {
...ImageFields
}
}
}
}
/public - Static Assets
Public files served at root URL:
public/
├── favicon.ico # Site favicon
├── logo.svg # Site logo
├── og-image.jpg # Default social image
└── robots.txt # Search engine rules
Configuration Files
next.config.ts
import type { NextConfig } from 'next';
const config: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'your-wordpress.com',
},
],
},
// Enable experimental features as needed
};
export default config;
tailwind.config.ts
import type { Config } from 'tailwindcss';
export default {
darkMode: ['class'],
content: [
'./app/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
],
theme: {
extend: {
colors: {
// Custom colors
},
},
},
plugins: [
require('@tailwindcss/typography'),
],
} satisfies Config;
codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: process.env.NEXT_PUBLIC_WORDPRESS_API_URL,
documents: ['graphql/**/*.graphql'],
generates: {
'graphql/generated/graphql.ts': {
plugins: [
'typescript',
'typescript-operations',
'typescript-graphql-request',
],
},
},
};
export default config;
File Naming Conventions
Components
- PascalCase for component files:
PostCard.tsx - index.ts for barrel exports:
components/ui/index.ts - kebab-case for non-component files:
use-posts.ts
Routes
- lowercase folder names:
app/blog/ - page.tsx for page components
- layout.tsx for layouts
- route.ts for API routes
GraphQL
- kebab-case for query files:
get-posts.graphql - PascalCase for operations:
GetPostBySlug
Import Aliases
FlatWP uses path aliases for cleaner imports:
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./*"],
"@/components/*": ["./components/*"],
"@/lib/*": ["./lib/*"],
"@/graphql/*": ["./graphql/*"]
}
}
}
Usage:
// Instead of
import { Button } from '../../../components/ui/button';
// Use
import { Button } from '@/components/ui/button';
Environment Files
.env.example # Template (commit this)
.env.local # Local development (don't commit)
.env.production # Production values (set in hosting)
Scripts
Available npm scripts in package.json:
| Script | Description |
|---|---|
dev | Start development server |
build | Build for production |
start | Run production build |
lint | Run ESLint |
type-check | Run TypeScript checks |
graphql:codegen | Generate GraphQL types |
setup | Interactive environment setup |
download-plugin | Download FlatWP plugin |
See Also
- Architecture - System design overview
- Quick Start - Get started guide
- WordPress Blocks - Block development