Project Structure - Tectonica-Campaigns-Solutions/Tectonica-Dev-Team-Standards-Practices GitHub Wiki
This guide defines our standard project structure for Next.js applications integrated with DatoCMS. Following these standards ensures consistency across projects, makes codebases predictable for team members, and facilitates maintenance and scalability.
- Directory Structure
- File Naming Conventions
- Component Organization
- Routing Structure
- API and Data Layer
- Configuration Files
- Asset Management
- Testing Structure
- Documentation
project-root/
├── .github/ # GitHub specific files
│ ├── workflows/ # CI/CD workflows
│ └── PULL_REQUEST_TEMPLATE.md
├── .husky/ # Git hooks
├── public/ # Static files
│ ├── fonts/ # Web fonts
│ ├── images/ # Static images
│ └── locales/ # Translation files
├── scripts/ # Build/utility scripts
├── src/ # Source code
├── tests/ # E2E tests
├── .env.example # Environment variables template
├── .eslintrc.json # ESLint configuration
├── .gitignore # Git ignore rules
├── .nvmrc # Node version
├── .prettierrc # Prettier configuration
├── README.md # Project documentation
├── next.config.js # Next.js configuration
├── package.json # Dependencies and scripts
├── tailwind.config.ts # Tailwind configuration
└── tsconfig.json # TypeScript configuration
src/
├── app/ # Next.js App Router
│ ├── (auth)/ # Route groups
│ ├── api/ # API routes
│ ├── [locale]/ # Internationalization
│ ├── error.tsx # Error boundary
│ ├── layout.tsx # Root layout
│ ├── loading.tsx # Loading state
│ └── page.tsx # Home page
├── components/ # React components
│ ├── common/ # Shared components
│ ├── forms/ # Form components
│ ├── layout/ # Layout components
│ ├── sections/ # Page sections
│ └── ui/ # UI primitives
├── hooks/ # Custom React hooks
├── lib/ # Core libraries
│ ├── datocms/ # DatoCMS integration
│ ├── utils/ # Utility functions
│ └── constants/ # App constants
├── styles/ # Global styles
├── types/ # TypeScript types
│ ├── generated/ # Auto-generated types
│ └── index.ts # Manual type definitions
└── middleware.ts # Next.js middleware
-
Use kebab-case for files and folders
✅ user-profile.tsx ✅ api-helpers.ts ❌ userProfile.tsx ❌ ApiHelpers.ts
-
Use PascalCase for component files
✅ UserProfile.tsx ✅ NavigationMenu.tsx ❌ user-profile.tsx ❌ navigationMenu.tsx
-
Use index files for cleaner imports
components/ └── Button/ ├── Button.tsx # Component implementation ├── Button.test.tsx # Component tests ├── Button.module.css # Component styles └── index.ts # Re-export
Components: PascalCase.tsx
- Button.tsx
- UserCard.tsx
- NavigationHeader.tsx
Hooks: camelCase with 'use' prefix
- useAuth.ts
- useLocalStorage.ts
- useDatoSubscription.ts
Utilities: camelCase.ts
- formatDate.ts
- parseUrl.ts
- validateEmail.ts
Types: PascalCase.ts or .d.ts
- User.ts
- ApiResponse.ts
- dato.d.ts
Constants: UPPER_SNAKE_CASE or camelCase
- API_ENDPOINTS.ts
- colorPalette.ts
- CONFIG.ts
Tests: [name].test.ts(x) or [name].spec.ts(x)
- Button.test.tsx
- formatDate.test.ts
- api.spec.ts
Each component should follow this structure:
components/
└── ComponentName/
├── ComponentName.tsx # Main component
├── ComponentName.test.tsx # Unit tests
├── ComponentName.stories.tsx # Storybook stories
├── ComponentName.module.css # CSS modules (if needed)
├── index.ts # Public API
└── types.ts # Component-specific types
Reusable across the entire application:
common/
├── Button/
├── Card/
├── Modal/
├── Spinner/
└── Typography/
Page structure and navigation:
layout/
├── Header/
├── Footer/
├── Sidebar/
├── Navigation/
└── PageContainer/
Form inputs and validation:
forms/
├── Input/
├── Select/
├── Checkbox/
├── RadioGroup/
└── FormField/
Page-specific sections:
sections/
├── Hero/
├── Features/
├── Testimonials/
├── CallToAction/
└── Newsletter/
Basic UI elements:
ui/
├── Badge/
├── Avatar/
├── Tooltip/
├── Dropdown/
└── Tabs/
// components/Button/Button.tsx
import { FC, ButtonHTMLAttributes } from 'react';
import clsx from 'clsx';
import styles from './Button.module.css';
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
}
export const Button: FC<ButtonProps> = ({
children,
variant = 'primary',
size = 'md',
isLoading = false,
className,
disabled,
...props
}) => {
return (
<button
className={clsx(
styles.button,
styles[variant],
styles[size],
{
[styles.loading]: isLoading,
},
className
)}
disabled={disabled || isLoading}
{...props}
>
{isLoading ? <Spinner /> : children}
</button>
);
};
// components/Button/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button';
app/
├── (marketing)/ # Marketing pages group
│ ├── about/
│ ├── contact/
│ └── pricing/
├── (app)/ # Application pages group
│ ├── dashboard/
│ ├── settings/
│ └── profile/
├── auth/ # Authentication pages
│ ├── login/
│ ├── register/
│ └── forgot-password/
├── api/ # API routes
│ ├── auth/
│ ├── webhooks/
│ └── revalidate/
└── [locale]/ # Internationalization
├── layout.tsx
└── page.tsx
Each route should contain:
route-name/
├── page.tsx # Page component
├── layout.tsx # Layout (if needed)
├── loading.tsx # Loading state
├── error.tsx # Error boundary
├── route.ts # API route (in api/ folder)
└── opengraph-image.tsx # OG image generation
app/
├── blog/
│ ├── [slug]/
│ │ ├── page.tsx
│ │ └── loading.tsx
│ └── page.tsx
└── products/
├── [category]/
│ └── [id]/
│ └── page.tsx
└── page.tsx
lib/
└── datocms/
├── client.ts # GraphQL client setup
├── queries/ # GraphQL queries
│ ├── pages.ts
│ ├── posts.ts
│ ├── products.ts
│ └── fragments.ts
├── types/ # Generated types
│ └── dato.ts
└── utils/ # DatoCMS utilities
├── imageLoader.ts
└── performRequest.ts
// lib/datocms/queries/posts.ts
import { gql } from 'graphql-request';
import { IMAGE_FRAGMENT, SEO_FRAGMENT } from './fragments';
export const ALL_POSTS_QUERY = gql`
query AllPosts($first: IntType, $skip: IntType) {
allPosts(first: $first, skip: $skip, orderBy: _publishedAt_DESC) {
id
title
slug
excerpt
publishedAt
coverImage {
...ImageFragment
}
author {
name
picture {
...ImageFragment
}
}
}
}
${IMAGE_FRAGMENT}
`;
export const POST_BY_SLUG_QUERY = gql`
query PostBySlug($slug: String!) {
post(filter: { slug: { eq: $slug } }) {
id
title
slug
content
publishedAt
seo {
...SeoFragment
}
coverImage {
...ImageFragment
}
author {
name
bio
picture {
...ImageFragment
}
}
}
}
${IMAGE_FRAGMENT}
${SEO_FRAGMENT}
`;
app/api/
├── auth/
│ ├── login/
│ │ └── route.ts
│ ├── logout/
│ │ └── route.ts
│ └── refresh/
│ └── route.ts
├── webhooks/
│ ├── dato/
│ │ └── route.ts
│ └── stripe/
│ └── route.ts
└── revalidate/
└── route.ts
.env.example:
- Template for environment variables
- Document all required variables
- Include example values
next.config.js:
- Next.js configuration
- Image domains
- Redirects and rewrites
- Environment variables
tsconfig.json:
- TypeScript configuration
- Path aliases
- Compiler options
.eslintrc.json:
- ESLint rules
- Custom configurations
- Plugin settings
.prettierrc:
- Code formatting rules
- Consistent styling
# .env.example
# Application
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_SITE_NAME="Your Site Name"
# DatoCMS
NEXT_PUBLIC_DATOCMS_API_TOKEN=
DATOCMS_API_TOKEN=
DATOCMS_PREVIEW_SECRET=
NEXT_PUBLIC_DATOCMS_ENVIRONMENT=main
# Authentication
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=
AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=
# External Services
STRIPE_PUBLIC_KEY=
STRIPE_SECRET_KEY=
SENDGRID_API_KEY=
# Analytics
NEXT_PUBLIC_GA_ID=
NEXT_PUBLIC_GTM_ID=
# Feature Flags
NEXT_PUBLIC_ENABLE_ANALYTICS=true
NEXT_PUBLIC_ENABLE_PWA=false
public/
├── fonts/ # Self-hosted fonts
│ ├── inter-var.woff2
│ └── fonts.css
├── images/ # Static images
│ ├── logo.svg
│ ├── og-image.png
│ └── icons/
│ ├── favicon.ico
│ └── apple-touch-icon.png
├── locales/ # Translation files
│ ├── en/
│ │ └── common.json
│ └── es/
│ └── common.json
└── manifest.json # PWA manifest
// Use Next.js Image for optimization
import Image from 'next/image';
// For DatoCMS images
import { Image as DatoImage } from 'react-datocms';
// For static images
import logoImg from '@/public/images/logo.png';
components/
└── icons/
├── ArrowIcon.tsx
├── CloseIcon.tsx
└── index.ts # Export all icons
tests/
├── e2e/ # End-to-end tests
│ ├── auth.spec.ts
│ ├── navigation.spec.ts
│ └── checkout.spec.ts
├── integration/ # Integration tests
│ ├── api/
│ └── datocms/
└── fixtures/ # Test data
├── users.json
└── products.json
# Unit tests co-located with components
components/Button/Button.test.tsx
lib/utils/formatDate.test.ts
Unit Tests: [name].test.ts(x)
Integration Tests: [name].integration.test.ts
E2E Tests: [name].spec.ts
Test Utilities: [name].test-utils.ts
Mock Data: [name].mock.ts
docs/
├── API.md # API documentation
├── DEPLOYMENT.md # Deployment guide
├── DEVELOPMENT.md # Development guide
├── ARCHITECTURE.md # Architecture decisions
└── CONTRIBUTING.md # Contribution guidelines
# Component documentation
components/Button/README.md
# Feature documentation
app/features/checkout/README.md
# Project Name
## Overview
Brief description of the project
## Tech Stack
- Next.js 14
- DatoCMS
- TypeScript
## Getting Started
1. Clone the repository
2. Install dependencies
3. Set up environment variables
4. Run development server
## Project Structure
Overview of folder organization
## Development
- Code standards
- Git workflow
- Testing approach
## Deployment
- Build process
- Environment management
- CI/CD pipeline
## Contributing
Link to contributing guidelines
- Consistent naming conventions
- Logical component grouping
- Clear separation of concerns
- Co-located tests and styles
- Proper use of index files
- One component per file
- Related files grouped together
- Clear import/export structure
- Proper TypeScript types location
- Documentation where needed
- DRY principle followed
- Reusable components extracted
- Business logic separated
- Utilities properly categorized
- Constants centralized
- RESTful URL structure
- Proper use of route groups
- Loading and error states
- SEO-friendly URLs
- Consistent naming
When restructuring existing projects:
-
Plan the migration
- Document current structure
- Map to new structure
- Identify breaking changes
-
Gradual migration
- Start with new features
- Refactor module by module
- Update imports progressively
-
Update tooling
- Update path aliases
- Fix build scripts
- Update test configurations
-
Team communication
- Document changes
- Update team guides
- Conduct training session
June 9, 2025 - Comprehensive structure guide created