How Reusable Components and Helpers Are Managed in Open-SEO
Open-SEO—an open-source SEO toolkit—organizes reusable logic in the src/shared directory as plain ES modules, while UI components live in src/client/features/*, enabling clean separation of concerns and flat dependency graphs.
All reusable code in every-app/open-seo follows a strict file-based architecture that prioritizes discoverability, type safety, and zero side-effects. Whether you're building a server function or a React component, the import path tells you exactly where the logic originates, making the codebase predictable at scale.
Core Architecture: The src/shared Directory
Business logic in Open-SEO is centralized under src/shared. Each file is narrowly scoped to a single domain and exports pure functions, constants, Zod schemas, or TypeScript types.
Key Helper Files
| File | Purpose |
|---|---|
src/shared/tag-colors.ts |
Maps tag names to color codes; consumed by UI and server functions alike |
src/shared/json.ts |
Safe JSON parsing with runtime validation |
src/shared/rank-tracking.ts |
Core data layer for rank-tracking operations |
src/shared/gsc.ts |
Google Search Console response normalizers |
src/shared/error-codes.ts |
Centralized error definitions |
These files are deliberately stateless and side-effect-free. As implemented in every-app/open-seo, this purity guarantees that helpers run identically in server functions, client bundles, and test environments.
Consuming Shared Helpers: Import Patterns
Direct Relative Imports
Most code imports helpers explicitly by file path. This keeps dependencies visible and avoids hidden coupling:
// src/serverFunctions/keywords.ts
import { parseJSON } from '@/shared/json';
import { getTagColor } from '@/shared/tag-colors';
export async function getKeywordData(id: string) {
const raw = await fetchKeywordFromDB(id);
const data = parseJSON(raw);
const color = getTagColor(data.priority);
return { ...data, color };
}
Barrel Exports for Convenience
Frequently used helpers are re-exported from src/shared/index.ts, reducing import verbosity:
// src/shared/index.ts
export * from './json';
export * from './tag-colors';
export * from './rank-tracking';
export * from './error-codes';
Downstream modules can then import multiple helpers from a single entry point:
// src/serverFunctions/rank-tracking.ts
import { trackRank, getRankHistory } from '@/shared';
Reusable UI Components in src/client/features
Presentation logic is separated from business logic. React components are organized by feature domain rather than technical type (e.g., "components" vs. "hooks").
Example: TagChip.tsx
// src/client/features/saved-keywords/TagChip.tsx
import { getTagColor } from '@/shared/tag-colors';
export function TagChip({ label }: { label: string }) {
const color = getTagColor(label);
return <span style={{ backgroundColor: color }}>{label}</span>;
}
Notice how TagChip consumes the same getTagColor helper used in server functions. This alignment prevents color logic drift between backend and frontend.
Example: SavedKeywordsTable.tsx
// src/client/features/saved-keywords/SavedKeywordsTable.tsx
import { getTagColor } from '@/shared/tag-colors';
import { usePagination } from '@/shared/pagination';
export function SavedKeywordsTable({ data }) {
const { page, setPage, paginatedData } = usePagination(data);
// ...
}
Type Safety Through Zod and TypeScript
Open-SEO enforces runtime and compile-time safety by pairing Zod schemas with TypeScript interfaces. In src/shared/json.ts and similar files, validators export both the schema and the inferred type:
// src/shared/json.ts (illustrative pattern)
import { z } from 'zod';
export const KeywordSchema = z.object({
id: z.string(),
priority: z.enum(['high', 'medium', 'low']),
});
export type Keyword = z.infer<typeof KeywordSchema>;
export function parseJSON(input: unknown): Keyword {
return KeywordSchema.parse(input);
}
Callers receive autocomplete from TypeScript while Zod guarantees the shape at runtime.
Design Principles Summary
- Single Responsibility — One file per domain; no kitchen-sink utilities
- Pure Functions — No external state, no DOM access, no fetch calls inside helpers
- Explicit Imports — Barrel files are optional; direct paths are preferred for clarity
- Cross-Layer Reuse — Same helper runs in server functions, React components, and tests
Summary
- Business logic lives in
src/sharedas plain ES modules exporting functions, constants, and Zod schemas - UI components reside in
src/client/features/*as composable React functions - Import patterns include direct file paths for clarity and
src/shared/index.tsfor convenience - Type safety is enforced through Zod validation with TypeScript inference
- Zero side-effects in helpers enables reuse across server, client, and test boundaries
Frequently Asked Questions
How do I add a new helper to Open-SEO?
Create a new file in src/shared/{domain}.ts with a descriptive name. Export pure functions and any associated Zod schemas. Add the export to src/shared/index.ts only if the helper is widely used across multiple modules.
Can shared helpers import from client-only or server-only code?
No. Files in src/shared must remain environment-agnostic. Avoid importing React, Node.js-specific modules, or any API that doesn't exist in both runtime contexts. This restriction preserves the ability to test and bundle helpers anywhere.
What's the difference between a helper in src/shared and a utility inside a feature folder?
src/shared is for cross-cutting concerns used by multiple features (e.g., JSON parsing, color mapping). Feature folders contain local utilities relevant only to that domain (e.g., a custom hook for saved-keywords filtering). When in doubt, start local and promote to src/shared upon second use.
How does Open-SEO prevent circular dependencies between shared modules?
Each file in src/shared is narrowly scoped and avoids importing sibling helpers unless absolutely necessary. The flat export structure and single-responsibility files naturally limit dependency depth. The codebase appears to rely on lint rules or manual review to catch cycles, as no automated guard is mentioned in the source analysis.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →