Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/document-ssg-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@effex/platform": patch
"create-effex": patch
---

Surface SSG support in package documentation: add a Rendering Modes overview and a dedicated Static Site Generation section to the top-level README, give SSG equal billing with SSR in `@effex/platform`'s README (including a full Quick Start, `buildStaticSite` API reference, and `BuildStaticSiteOptions` shape), and document the SSG template, `--ssg` flag, project structure, and build commands in `create-effex`'s README. No code changes.
61 changes: 57 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,43 @@ Effex gives you access to Effect's entire ecosystem:
- **Retry/timeout** β€” Built-in resilience patterns
- **Structured concurrency** β€” Fork, join, and race without footguns

## Rendering Modes

Effex supports three rendering modes. Pick based on what your app needs at runtime:

| Mode | When to use | Output | Hosting |
|------|-------------|--------|---------|
| **SPA** | App needs no server logic; all data fetched client-side. Internal tools, dashboards. | Client bundle + `index.html` | Any static host |
| **SSR** | Per-request server logic (auth, dynamic data, mutations). Web apps with database access. | Long-running Node HTTP server + client bundle | Fly.io, Railway, VPS, Cloud Run |
| **SSG** | Content is known at build time. Portfolios, marketing sites, docs, blogs. | Pre-rendered HTML pages + client bundle for hydration | Any static host (Cloudflare Pages, Netlify, GitHub Pages) |

All three produce interactive, hydrated pages β€” SSG output behaves identically to SSR output once the client bundle loads. The difference is purely *where rendering happens* (build time vs. request time vs. browser).

## Quick Start

```bash
# Create a new project
# Create a new project β€” prompts you to pick SPA, SSR, or SSG
pnpm create effex my-app
cd my-app
pnpm install
pnpm dev
```

Skip the prompt with a template flag:

```bash
pnpm create effex my-app --spa # SPA
pnpm create effex my-app --ssr # SSR
pnpm create effex my-app --ssg # SSG
```

Or install packages individually:

```bash
# SPA (client-side only)
pnpm add @effex/dom @effex/router effect

# Full-stack SSR
# Full-stack SSR / SSG
pnpm add @effex/dom @effex/router @effex/platform @effect/platform effect
```

Expand Down Expand Up @@ -286,7 +306,7 @@ LoginForm.provide(

Supports leaf fields, nested structs, arrays, and maps β€” all with Effect Schema validation.

## Full-Stack SSR
## Server-Side Rendering (SSR)

`@effex/platform` bridges Effex with `@effect/platform`'s HTTP server for server-side rendering:

Expand Down Expand Up @@ -324,6 +344,39 @@ Key features:
- **Redirects** β€” Throw `RedirectError` from loaders for server-side redirects
- **HttpApi composition** β€” Mount Effect's HttpApi alongside Effex pages on a single server

## Static Site Generation (SSG)

The same `@effex/platform` package also supports building fully static sites. Routes opt in via `Route.static`, which declares the paths to generate and a build-time loader:

```ts
// routes.ts
import { Route, Router } from "@effex/router";

const PostRoute = Route.make("/posts/:slug").pipe(
Route.params(Schema.Struct({ slug: Schema.String })),
Route.static({
paths: () => Effect.succeed([{ slug: "hello" }, { slug: "world" }]),
load: ({ params }) => loadPostFromDisk(params.slug),
render: (post) => PostPage({ post }),
}),
);
```

Build to a `dist/` directory of static HTML at build time:

```ts
// vite.config.ts
import { effexPlatform } from "@effex/vite-plugin";

export default defineConfig({
plugins: [effexPlatform({ mode: "ssg", entry: "src/entry.ts" })],
});
```

Output is fully hydratable β€” the generated HTML embeds loader data via `window.__EFFEX_DATA__`, and the client bundle picks up where the server left off. Animations, interactive components, signal-driven UI all work post-hydration. Deploy to any static host (Cloudflare Pages, Netlify, GitHub Pages, S3 + CloudFront).

See [`@effex/platform`](./packages/platform) for the full `buildStaticSite` API.

## Packages

| Package | Description |
Expand All @@ -334,7 +387,7 @@ Key features:
| [`@effex/form`](./packages/form) | Schema-validated forms with reactive field state |
| [`@effex/platform`](./packages/platform) | Server-side rendering, hydration, and data loading |
| [`@effex/vite-plugin`](./packages/vite-plugin) | Vite plugin: SSR dev server + server-code stripping |
| [`create-effex`](./packages/create-effex) | CLI to scaffold new projects (SPA or SSR) |
| [`create-effex`](./packages/create-effex) | CLI to scaffold new projects (SPA, SSR, or SSG) |

**Import conventions:**
- `@effex/dom` re-exports everything from `@effex/core` β€” no need to install core separately
Expand Down
41 changes: 41 additions & 0 deletions packages/create-effex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ Includes:
- Production server with static file serving
- Client hydration entry point

### SSG (Static Site Generation)

A pre-rendered static site with client-side hydration. Good for portfolios, marketing sites, documentation, and blogs:

```bash
pnpm create effex my-app --ssg
```

Includes:
- `@effex/dom` β€” DOM rendering and reactivity
- `@effex/router` β€” Routing (shared between build and client)
- `@effex/platform` β€” Static site generation via `buildStaticSite`
- `@effex/vite-plugin` configured in `ssg` mode β€” runs the static build after the client bundle
- Client hydration entry point

Routes opt into static generation via `Route.static({ paths, load, render })`. The `paths` function returns all parameter sets to build; the `load` function runs at build time per path. Output is fully hydratable β€” animations and interactive components work the same as SSR once the client bundle loads.

## Project Structure

### SPA Template
Expand Down Expand Up @@ -85,6 +102,20 @@ my-app/
└── package.json
```

### SSG Template

```
my-app/
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ App.ts # Root layout (shared build/client)
β”‚ β”œβ”€β”€ client.ts # Client hydration entry
β”‚ β”œβ”€β”€ entry.ts # Vite SSG entry (exports router + app + document)
β”‚ └── routes.ts # Route definitions and router
β”œβ”€β”€ vite.config.ts
β”œβ”€β”€ tsconfig.json
└── package.json
```

## Development

After creating your project:
Expand All @@ -111,6 +142,15 @@ pnpm build # Build client + server bundles
pnpm start # Run production server
```

### SSG

```bash
pnpm build # Build client bundle + generate static HTML for all Route.static routes
pnpm preview # Preview the static site locally
```

The build outputs static HTML pages plus a hashed client bundle to `dist/`. Deploy `dist/` to any static host.

## CLI Options

```
Expand All @@ -119,6 +159,7 @@ create-effex <project-name> [options]
Options:
--spa Use SPA template
--ssr Use SSR template
--ssg Use SSG template
--no-install Skip dependency installation
--help Show help
--version Show version
Expand Down
Loading
Loading