# How Reusable Components and Helpers Are Managed in Open-SEO

> Discover how Open-SEO manages reusable components and helpers in its `src/shared` and `src/client/features` directories. Learn about its clean separation of concerns and flat dependency graphs for efficient development.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: internals
- Published: 2026-08-08

---

**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`](https://github.com/every-app/open-seo/blob/main/src/shared/tag-colors.ts) | Maps tag names to color codes; consumed by UI and server functions alike |
| [`src/shared/json.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/json.ts) | Safe JSON parsing with runtime validation |
| [`src/shared/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts) | Core data layer for rank-tracking operations |
| [`src/shared/gsc.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/gsc.ts) | Google Search Console response normalizers |
| [`src/shared/error-codes.ts`](https://github.com/every-app/open-seo/blob/main/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:

```typescript
// 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`](https://github.com/every-app/open-seo/blob/main/src/shared/index.ts), reducing import verbosity:

```typescript
// 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:

```typescript
// 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`](https://github.com/every-app/open-seo/blob/main/TagChip.tsx)

```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`](https://github.com/every-app/open-seo/blob/main/SavedKeywordsTable.tsx)

```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`](https://github.com/every-app/open-seo/blob/main/src/shared/json.ts) and similar files, validators export both the schema and the inferred type:

```typescript
// 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/shared` as 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.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/index.ts) for 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`](https://github.com/every-app/open-seo/blob/main/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.