Jen.js has a clear, conventional project structure. Understand it to build effective applications.

Directory Layout

jen.js-app/
├── site/                    # Application routes and pages
│   ├── (home).tsx          # Routes in Preact/JSX
│   ├── (about).tsx
│   ├── posts/
│   │   ├── (index).tsx     # /posts → index page
│   │   └── ($id).tsx       # /posts/:id → dynamic page
│   └── api/
│       ├── (users).ts      # /api/users → API endpoint
│       └── v1/
│           └── (data).ts   # /api/v1/data → nested API
│
├── src/                     # Framework and app code
│   ├── components/         # Reusable Preact components
│   │   ├── header.tsx
│   │   ├── footer.tsx
│   │   └── nav.tsx
│   ├── lib/                # Utility functions
│   │   ├── db.ts          # Database helpers
│   │   ├── api.ts         # API client
│   │   └── utils.ts       # Utilities
│   ├── middleware/         # Express-style middleware
│   │   ├── auth.ts
│   │   └── logging.ts
│   ├── plugins/            # Custom plugins
│   │   └── my-plugin.ts
│   └── styles/             # Stylesheets
│       ├── globals.css
│       └── components.css
│
├── public/                 # Static assets (copied as-is)
│   ├── favicon.ico
│   ├── robots.txt
│   └── images/
│
├── dist/                   # Build output (generated)
│   ├── index/
│   │   └── index.html     # Rendered pages
│   ├── api/               # API routes (if SSR)
│   ├── images/
│   ├── styles.css
│   └── bundle.js
│
├── .env                    # Environment variables (not in git)
├── .gitignore
├── jen.config.ts          # Framework configuration
├── tsconfig.json          # TypeScript configuration
├── package.json           # Dependencies and scripts
└── README.md

Key Directories

site/

Your application code lives here. This is where you create pages and API routes.

Rules: - Files must match pattern: (name).tsx or (name).ts - Files become routes automatically - Directories create nested routes - .ts files are API routes - .tsx files are pages (render HTML)

Examples:

site/(home).tsx          → GET /
site/(about).tsx         → GET /about
site/blog/($slug).tsx    → GET /blog/:slug
site/api/(users).ts      → GET /api/users

src/

Optional code supporting your application.

Subdirectories: - components/ — Reusable Preact components - lib/ — Utility functions and helpers - middleware/ — Custom middleware - plugins/ — Custom plugins - styles/ — CSS/SCSS files - types/ — TypeScript type definitions

Accessed via path alias:

import { Header } from '@src/components/header';
import { db } from '@src/lib/db';

public/

Static assets that are copied to dist/ unchanged.

Usage: - Put images in public/images/ - Reference as /images/logo.png in HTML - Includes robots.txt, sitemaps, etc.

dist/

Generated output from build. Ignored by git.

Structure after build:

dist/
├── index/               # Page routes
│   ├── index.html      # / route
│   ├── about/
│   │   └── index.html  # /about
│   └── posts/
│       └── 1/
│           └── index.html
├── api/                # API routes (SSR only)
└── assets/             # Static files

File Organization

Components

Create reusable components in src/components/:

// src/components/button.tsx
interface ButtonProps {
  text: string;
  onClick: () => void;
}

export function Button({ text, onClick }: ButtonProps) {
  return <button onClick={onClick}>{text}</button>;
}

Use in pages:

// site/(home).tsx
import { Button } from '@src/components/button';

export default function Home() {
  return <Button text="Click me" onClick={() => alert('Clicked!')} />;
}

Utilities

Create helpers in src/lib/:

// src/lib/api.ts
export async function fetchUser(id: string) {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

// src/lib/validation.ts
export function validateEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

Use throughout:

import { fetchUser } from '@src/lib/api';
import { validateEmail } from '@src/lib/validation';

Styles

Keep styles organized:

src/styles/
├── globals.css         # Global styles
├── layout.css          # Layout components
├── typography.css      # Typography
└── variables.css       # CSS variables

Import in pages:

// site/(home).tsx
import '@src/styles/globals.css';

export default function Home() {
  return <h1>Styled page</h1>;
}

Or use inline styles:

const styles = `
  h1 { color: blue; }
  p { font-size: 16px; }
`;

export default function Home() {
  return (
    <html>
      <head><style>{styles}</style></head>
      <body><h1>Heading</h1></body>
    </html>
  );
}

Types

Create type definitions in src/types/:

// src/types/user.ts
export interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}

export interface UserInput {
  name: string;
  email: string;
}

Use throughout:

import type { User } from '@src/types/user';

export async function getUser(id: string): Promise<User> {
  // ...
}

Configuration Files

jen.config.ts

Main framework configuration:

import type { FrameworkConfig } from '@src/core/config';

const config: FrameworkConfig = {
  siteDir: 'site',
  distDir: 'dist',
  // ... more options
};

export default config;

tsconfig.json

TypeScript configuration:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2020",
    "jsx": "react-jsx",
    "jsxImportSource": "preact",
    "strict": true,
    "paths": {
      "@src/*": ["src/*"]
    }
  }
}

.env

Environment variables (not committed):

DATABASE_URL=sqlite://./data.db
API_KEY=secret123

package.json

Dependencies and scripts:

{
  "scripts": {
    "dev": "node server.ts dev",
    "build": "node build.js",
    "start": "node server.ts start",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "preact": "^10.0.0"
  }
}

Build Output Structure

After running npm run build:

dist/
├── index/
│   ├── index.html          # / (homepage)
│   ├── about/
│   │   └── index.html      # /about
│   ├── posts/
│   │   ├── 1/
│   │   │   └── index.html  # /posts/1
│   │   └── 2/
│   │       └── index.html  # /posts/2
│   └── api/                # /api/* routes (if SSR)
├── styles.css
├── bundle.js
└── assets/
    ├── images/
    └── fonts/

For SSR mode:

dist/
├── server.js               # Runtime server
├── api/                    # API route handlers
│   └── users.js
└── pages/                  # Page components
    └── home.js

Organization Best Practices

Group by Feature

For larger apps, organize by feature:

src/
├── features/
│   ├── auth/
│   │   ├── components/
│   │   ├── lib/
│   │   └── types.ts
│   ├── users/
│   │   ├── components/
│   │   ├── lib/
│   │   └── types.ts
│   └── posts/
│       ├── components/
│       ├── lib/
│       └── types.ts

Shared Code

Keep truly shared code in src/:

src/
├── components/      # Shared components
├── lib/            # Shared utilities
├── types/          # Shared types
└── middleware/     # Shared middleware

Keep site/ Simple

The site/ directory should mainly be route definitions that delegate to components:

// site/(user).tsx
import { UserProfile } from '@src/features/users/components/profile';
import { getUser } from '@src/features/users/lib/api';

export async function loader(ctx: any) {
  return { user: await getUser(ctx.params.id) };
}

export default function UserPage({ data }: any) {
  return <UserProfile user={data.user} />;
}

Next Steps

  • Create your first component
  • Organize your project
  • Build your application