@lolyjs/core 0.2.0-alpha.14 → 0.2.0-alpha.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,755 +1,1016 @@
1
- # Loly Framework
2
-
3
- <div align="center">
4
-
5
- **A modern, full-stack React framework with native WebSocket support, route-level middlewares, and enterprise-grade features**
6
-
7
- [![npm version](https://img.shields.io/npm/v/@lolyjs/core?style=flat-square)](https://www.npmjs.com/package/@lolyjs/core)
8
- [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg?style=flat-square)](https://opensource.org/licenses/ISC)
9
- ![Downloads](https://img.shields.io/npm/dm/@lolyjs/core)
10
- <br>
11
- [![Alpha](https://img.shields.io/badge/status-alpha-red.svg)](https://github.com/MenvielleValen/loly-framework)
12
- [![Expermiental](https://img.shields.io/badge/phase-experimental-black.svg)](https://github.com/MenvielleValen/loly-framework)
13
-
14
- _Built with React 19, Express, Rspack, Socket.IO, and TypeScript_
15
-
16
- </div>
17
-
18
- ---
19
-
20
- ## Getting Started
21
-
22
- Create a new Loly application in seconds:
23
-
24
- ```bash
25
- npx create-loly-app mi-app
26
- ```
27
-
28
- This will create a new project with all the necessary files and dependencies. For more information about the CLI, visit the [@lolyjs/cli package](https://www.npmjs.com/package/@lolyjs/cli).
29
-
30
- ---
31
-
32
- ## Overview
33
-
34
- Loly is a full-stack React framework that combines the simplicity of file-based routing with powerful server-side rendering, static site generation, and unique features like native WebSocket support and route-level middlewares.
35
-
36
- ### What Makes Loly Different?
37
-
38
- - 🔌 **Native WebSocket Support** - Built-in Socket.IO integration with automatic namespace routing
39
- - 🎯 **Route-Level Middlewares** - Define middlewares directly in your routes for pages and APIs
40
- - 📁 **Separation of Concerns** - Server logic in `server.hook.ts` separate from React components
41
- - 🚀 **Hybrid Rendering** - SSR, SSG, and CSR with streaming support
42
- - 🛡️ **Security First** - Built-in rate limiting, validation, sanitization, and security headers
43
- - ⚡ **Performance** - Fast bundling with Rspack and optimized code splitting
44
-
45
- ---
46
-
47
- ## Quick Start
48
-
49
- ### Installation
50
-
51
- ```bash
52
- npm install @lolyjs/core react react-dom
53
- # or
54
- pnpm add @lolyjs/core react react-dom
55
- ```
56
-
57
- ### Create Your First Page
58
-
59
- ```tsx
60
- // app/page.tsx
61
- export default function Home() {
62
- return <h1>Hello, Loly!</h1>;
63
- }
64
- ```
65
-
66
- ### Add Server-Side Data
67
-
68
- ```tsx
69
- // app/page/server.hook.ts
70
- import type { ServerLoader } from "@lolyjs/core";
71
-
72
- export const getServerSideProps: ServerLoader = async (ctx) => {
73
- const data = await fetchData();
74
-
75
- return {
76
- props: { data },
77
- metadata: {
78
- title: "Home Page",
79
- description: "Welcome to Loly",
80
- },
81
- };
82
- };
83
- ```
84
-
85
- ```tsx
86
- // app/page.tsx
87
- export default function Home({ props }) {
88
- return <h1>{props.data}</h1>;
89
- }
90
- ```
91
-
92
- ### Start Development Server
93
-
94
- ```bash
95
- npx loly dev
96
- # Server runs on http://localhost:3000
97
- ```
98
-
99
- ---
100
-
101
- ## Key Features
102
-
103
- ### 🔌 Native WebSocket Support
104
-
105
- Loly includes built-in WebSocket support with automatic namespace routing. Define WebSocket events using the same file-based routing pattern as pages and APIs:
106
-
107
- ```tsx
108
- // app/wss/chat/events.ts
109
- import type { WssContext } from "@lolyjs/core";
110
-
111
- export const events = [
112
- {
113
- name: "connection",
114
- handler: (ctx: WssContext) => {
115
- console.log("Client connected:", ctx.socket.id);
116
- },
117
- },
118
- {
119
- name: "message",
120
- handler: (ctx: WssContext) => {
121
- const { data, actions } = ctx;
122
- // Broadcast to all clients
123
- actions.broadcast("message", {
124
- text: data.text,
125
- from: ctx.socket.id,
126
- });
127
- },
128
- },
129
- ];
130
- ```
131
-
132
- **Client-side:**
133
-
134
- ```tsx
135
- import { lolySocket } from "@lolyjs/core/sockets";
136
-
137
- const socket = lolySocket("/chat");
138
-
139
- socket.on("message", (data) => {
140
- console.log("Received:", data);
141
- });
142
-
143
- socket.emit("message", { text: "Hello!" });
144
- ```
145
-
146
- **Key Benefits:**
147
-
148
- - Automatic namespace creation from file structure
149
- - Same routing pattern as pages and APIs
150
- - Built-in broadcasting helpers (`emit`, `broadcast`, `emitTo`, `emitToClient`)
151
- - No manual configuration required
152
-
153
- ### 🎯 Route-Level Middlewares
154
-
155
- Define middlewares directly in your routes for fine-grained control:
156
-
157
- **For Pages:**
158
-
159
- ```tsx
160
- // app/dashboard/server.hook.ts
161
- import type { RouteMiddleware, ServerLoader } from "@lolyjs/core";
162
-
163
- export const beforeServerData: RouteMiddleware[] = [
164
- async (ctx, next) => {
165
- // Authentication
166
- const token = ctx.req.headers.authorization;
167
- if (!token) {
168
- ctx.res.status(401).json({ error: "Unauthorized" });
169
- return;
170
- }
171
- ctx.locals.user = await verifyToken(token);
172
- await next();
173
- },
174
- ];
175
-
176
- export const getServerSideProps: ServerLoader = async (ctx) => {
177
- const user = ctx.locals.user; // Available from middleware
178
- return { props: { user } };
179
- };
180
- ```
181
-
182
- **For API Routes:**
183
-
184
- ```tsx
185
- // app/api/protected/route.ts
186
- import type { ApiMiddleware, ApiContext } from "@lolyjs/core";
187
-
188
- // Global middleware for all methods
189
- export const beforeApi: ApiMiddleware[] = [
190
- async (ctx, next) => {
191
- // Authentication
192
- const user = await verifyUser(ctx.req);
193
- ctx.locals.user = user;
194
- await next();
195
- },
196
- ];
197
-
198
- // Method-specific middleware
199
- export const beforePOST: ApiMiddleware[] = [
200
- async (ctx, next) => {
201
- // Validation specific to POST
202
- await next();
203
- },
204
- ];
205
-
206
- export async function GET(ctx: ApiContext) {
207
- const user = ctx.locals.user;
208
- return ctx.Response({ user });
209
- }
210
- ```
211
-
212
- **Key Benefits:**
213
-
214
- - Middlewares execute before loaders/handlers
215
- - Share data via `ctx.locals`
216
- - Method-specific middlewares for APIs
217
- - Clean separation of concerns
218
-
219
- ### 📁 File-Based Routing
220
-
221
- Routes are automatically created from your file structure:
222
-
223
- | File Path | Route |
224
- | ----------------------------- | --------------------- |
225
- | `app/page.tsx` | `/` |
226
- | `app/about/page.tsx` | `/about` |
227
- | `app/blog/[slug]/page.tsx` | `/blog/:slug` |
228
- | `app/post/[...path]/page.tsx` | `/post/*` (catch-all) |
229
-
230
- **Nested Layouts:**
231
-
232
- **⚠️ Important**: Layouts should NOT include `<html>` or `<body>` tags. The framework automatically handles the base HTML structure. Layouts should only contain content that goes inside the body.
233
-
234
- ```tsx
235
- // app/layout.tsx (Root layout)
236
- export default function RootLayout({ children }) {
237
- return (
238
- <div>
239
- <nav>Navigation</nav>
240
- {children}
241
- <footer>Footer</footer>
242
- </div>
243
- );
244
- }
245
- ```
246
-
247
- ```tsx
248
- // app/blog/layout.tsx (Nested layout)
249
- export default function BlogLayout({ children }) {
250
- return (
251
- <div>
252
- <aside>Sidebar</aside>
253
- <main>{children}</main>
254
- </div>
255
- );
256
- }
257
- ```
258
-
259
- ### 🚀 Hybrid Rendering
260
-
261
- Choose the best rendering strategy for each page:
262
-
263
- **SSR (Server-Side Rendering):**
264
-
265
- ```tsx
266
- // app/posts/server.hook.ts
267
- export const dynamic = "force-dynamic" as const;
268
-
269
- export const getServerSideProps: ServerLoader = async (ctx) => {
270
- const posts = await fetchFreshPosts();
271
- return { props: { posts } };
272
- };
273
- ```
274
-
275
- **SSG (Static Site Generation):**
276
-
277
- ```tsx
278
- // app/blog/[slug]/server.hook.ts
279
- export const dynamic = "force-static" as const;
280
-
281
- export const generateStaticParams: GenerateStaticParams = async () => {
282
- const posts = await getAllPosts();
283
- return posts.map((post) => ({ slug: post.slug }));
284
- };
285
-
286
- export const getServerSideProps: ServerLoader = async (ctx) => {
287
- const post = await getPost(ctx.params.slug);
288
- return { props: { post } };
289
- };
290
- ```
291
-
292
- **CSR (Client-Side Rendering):**
293
-
294
- ```tsx
295
- // app/dashboard/page.tsx (No server.hook.ts)
296
- import { useState, useEffect } from "react";
297
-
298
- export default function Dashboard() {
299
- const [data, setData] = useState(null);
300
-
301
- useEffect(() => {
302
- fetchData().then(setData);
303
- }, []);
304
-
305
- return <div>{data}</div>;
306
- }
307
- ```
308
-
309
- ### 🔌 API Routes
310
-
311
- Create RESTful APIs with flexible middleware support:
312
-
313
- ```tsx
314
- // app/api/posts/route.ts
315
- import type { ApiContext } from "@lolyjs/core";
316
- import { validate } from "@lolyjs/core";
317
- import { z } from "zod";
318
-
319
- const postSchema = z.object({
320
- title: z.string().min(1),
321
- content: z.string().min(1),
322
- });
323
-
324
- export async function GET(ctx: ApiContext) {
325
- const posts = await getPosts();
326
- return ctx.Response({ posts });
327
- }
328
-
329
- export async function POST(ctx: ApiContext) {
330
- const data = validate(postSchema, ctx.req.body);
331
- const post = await createPost(data);
332
- return ctx.Response({ post }, 201);
333
- }
334
- ```
335
-
336
- ### 🛡️ Built-in Security
337
-
338
- **Rate Limiting:**
339
-
340
- ```tsx
341
- // loly.config.ts
342
- import { ServerConfig } from "@lolyjs/core";
343
-
344
- export const config = (env: string): ServerConfig => {
345
- return {
346
- rateLimit: {
347
- windowMs: 15 * 60 * 1000, // 15 minutes
348
- max: 1000,
349
- strictMax: 5,
350
- strictPatterns: ["/api/auth/**"],
351
- },
352
- };
353
- };
354
- ```
355
-
356
- **Validation with Zod:**
357
-
358
- ```tsx
359
- import { validate, ValidationError } from "@lolyjs/core";
360
- import { z } from "zod";
361
-
362
- const schema = z.object({
363
- email: z.string().email(),
364
- age: z.number().int().min(0).max(150),
365
- });
366
-
367
- try {
368
- const data = validate(schema, req.body);
369
- } catch (error) {
370
- if (error instanceof ValidationError) {
371
- return Response({ errors: error.format() }, 400);
372
- }
373
- }
374
- ```
375
-
376
- **Automatic Sanitization:**
377
-
378
- Route parameters and query strings are automatically sanitized to prevent XSS attacks.
379
-
380
- **Security Headers:**
381
-
382
- Helmet is configured by default with CSP (Content Security Policy) and nonce support.
383
-
384
- ### 📝 Structured Logging
385
-
386
- ```tsx
387
- import { getRequestLogger, createModuleLogger } from "@lolyjs/core";
388
-
389
- // Request logger (automatic request ID)
390
- export const getServerSideProps: ServerLoader = async (ctx) => {
391
- const logger = getRequestLogger(ctx.req);
392
- logger.info("Processing request", { userId: ctx.locals.user?.id });
393
- return { props: {} };
394
- };
395
-
396
- // Module logger
397
- const logger = createModuleLogger("my-module");
398
- logger.info("Module initialized");
399
- logger.error("Error occurred", error);
400
- ```
401
-
402
- ---
403
-
404
- ## Project Structure
405
-
406
- ```
407
- your-app/
408
- ├── app/
409
- │ ├── layout.tsx # Root layout
410
- │ ├── page.tsx # Home page (/)
411
- │ ├── server.hook.ts # Server logic for home
412
- │ ├── _not-found.tsx # Custom 404
413
- │ ├── _error.tsx # Custom error page
414
- │ ├── blog/
415
- │ │ ├── layout.tsx # Blog layout
416
- │ │ ├── page.tsx # /blog
417
- │ │ └── [slug]/
418
- │ │ ├── page.tsx # /blog/:slug
419
- │ │ └── server.hook.ts # Server logic
420
- │ ├── api/
421
- │ │ └── posts/
422
- │ │ └── route.ts # /api/posts
423
- │ └── wss/
424
- │ └── chat/
425
- │ └── events.ts # WebSocket namespace /chat
426
- ├── components/ # React components
427
- ├── lib/ # Utilities
428
- ├── public/ # Static files
429
- ├── loly.config.ts # Framework configuration
430
- ├── init.server.ts # Server initialization (DB, services, etc.)
431
- └── package.json
432
- ```
433
-
434
- ---
435
-
436
- ## API Reference
437
-
438
- ### Server Loader
439
-
440
- ```tsx
441
- import type { ServerLoader } from "@lolyjs/core";
442
-
443
- export const getServerSideProps: ServerLoader = async (ctx) => {
444
- const { req, res, params, pathname, locals } = ctx;
445
-
446
- // Fetch data
447
- const data = await fetchData();
448
-
449
- // Redirect
450
- return {
451
- redirect: {
452
- destination: "/new-path",
453
- permanent: true,
454
- },
455
- };
456
-
457
- // Not found
458
- return { notFound: true };
459
-
460
- // Return props
461
- return {
462
- props: { data },
463
- metadata: {
464
- title: "Page Title",
465
- description: "Page description",
466
- },
467
- };
468
- };
469
- ```
470
-
471
- ### API Route Handler
472
-
473
- ```tsx
474
- import type { ApiContext } from "@lolyjs/core";
475
-
476
- export async function GET(ctx: ApiContext) {
477
- return ctx.Response({ data: "value" });
478
- }
479
-
480
- export async function POST(ctx: ApiContext) {
481
- return ctx.Response({ created: true }, 201);
482
- }
483
-
484
- export async function DELETE(ctx: ApiContext) {
485
- return ctx.Response({ deleted: true }, 204);
486
- }
487
- ```
488
-
489
- ### WebSocket Event Handler
490
-
491
- ```tsx
492
- import type { WssContext } from "@lolyjs/core";
493
-
494
- export const events = [
495
- {
496
- name: "connection",
497
- handler: (ctx: WssContext) => {
498
- // Handle connection
499
- },
500
- },
501
- {
502
- name: "custom-event",
503
- handler: (ctx: WssContext) => {
504
- const { socket, data, actions } = ctx;
505
-
506
- // Emit to all clients
507
- actions.emit("response", { message: "Hello" });
508
-
509
- // Broadcast to all except sender
510
- actions.broadcast("notification", data);
511
-
512
- // Emit to specific socket
513
- actions.emitTo(socketId, "private", data);
514
- },
515
- },
516
- ];
517
- ```
518
-
519
- ### Client Cache
520
-
521
- ```tsx
522
- import { revalidate } from "@lolyjs/core/client-cache";
523
-
524
- export default function Page({ props }) {
525
- const handleRefresh = async () => {
526
- await revalidate(); // Refresh current page data
527
- };
528
-
529
- return <div>{/* Your UI */}</div>;
530
- }
531
- ```
532
-
533
- ### Components
534
-
535
- ```tsx
536
- import { Link } from "@lolyjs/core/components";
537
-
538
- export default function Navigation() {
539
- return (
540
- <nav>
541
- <Link href="/">Home</Link>
542
- <Link href="/about">About</Link>
543
- <Link href="/blog/[slug]" params={{ slug: "my-post" }}>
544
- My Post
545
- </Link>
546
- </nav>
547
- );
548
- }
549
- ```
550
-
551
- ---
552
-
553
- ## Configuration
554
-
555
- ### Framework Configuration
556
-
557
- Create `loly.config.ts` in your project root to configure the framework:
558
-
559
- ```tsx
560
- import { FrameworkConfig } from "@lolyjs/core";
561
-
562
- export default {
563
- directories: {
564
- app: "app",
565
- build: ".loly",
566
- static: "public",
567
- },
568
- server: {
569
- port: 3000,
570
- host: "localhost",
571
- },
572
- routing: {
573
- trailingSlash: "ignore",
574
- caseSensitive: false,
575
- basePath: "",
576
- },
577
- rendering: {
578
- framework: "react",
579
- streaming: true,
580
- ssr: true,
581
- ssg: true,
582
- },
583
- } satisfies FrameworkConfig;
584
- ```
585
-
586
- ### Server Configuration
587
-
588
- Configure server settings (CORS, rate limiting, etc.) in `loly.config.ts` by exporting a `config` function:
589
-
590
- ```tsx
591
- // loly.config.ts
592
- import { ServerConfig } from "@lolyjs/core";
593
-
594
- export const config = (env: string): ServerConfig => {
595
- return {
596
- bodyLimit: "1mb",
597
- corsOrigin: env === "production" ? ["https://yourdomain.com"] : "*",
598
- rateLimit: {
599
- windowMs: 15 * 60 * 1000,
600
- max: 1000,
601
- strictMax: 5,
602
- strictPatterns: ["/api/auth/**"],
603
- },
604
- };
605
- };
606
- ```
607
-
608
- ### Server Initialization
609
-
610
- Create `init.server.ts` in your project root to initialize services when Express starts (database connections, external services, etc.):
611
-
612
- ```tsx
613
- // init.server.ts
614
- import { InitServerData } from "@lolyjs/core";
615
-
616
- export async function init({
617
- serverContext,
618
- }: {
619
- serverContext: InitServerData;
620
- }) {
621
- // Initialize database connection
622
- await connectToDatabase();
623
-
624
- // Setup external services
625
- await setupExternalServices();
626
-
627
- // Any other initialization logic
628
- console.log("Server initialized successfully");
629
- }
630
- ```
631
-
632
- **Note**: `init.server.ts` is for initializing your application services, not for configuring Loly Framework. Framework configuration goes in `loly.config.ts`.
633
-
634
- ---
635
-
636
- ## CLI Commands
637
-
638
- ```bash
639
- # Development server
640
- npx loly dev
641
-
642
- # Build for production
643
- npx loly build
644
-
645
- # Start production server
646
- npx loly start
647
- ```
648
-
649
- ---
650
-
651
- ## TypeScript Support
652
-
653
- Loly is built with TypeScript and provides full type safety:
654
-
655
- ```tsx
656
- import type {
657
- ServerContext,
658
- ServerLoader,
659
- ApiContext,
660
- WssContext,
661
- RouteMiddleware,
662
- ApiMiddleware,
663
- GenerateStaticParams,
664
- } from "@lolyjs/core";
665
- ```
666
-
667
- ---
668
-
669
- ## Production
670
-
671
- ### Build
672
-
673
- ```bash
674
- npm run build
675
- ```
676
-
677
- This generates:
678
-
679
- - Client bundle (`.loly/client`)
680
- - Static pages if using SSG (`.loly/ssg`)
681
- - Server code (`.loly/server`)
682
-
683
- ### Environment Variables
684
-
685
- ```bash
686
- PORT=3000
687
- HOST=0.0.0.0
688
- NODE_ENV=production
689
- PUBLIC_WS_BASE_URL=http://localhost:3000
690
- ```
691
-
692
- ---
693
-
694
- ## Exports
695
-
696
- ```tsx
697
- // Server
698
- import { startDevServer, startProdServer, buildApp } from "@lolyjs/core";
699
-
700
- // Types
701
- import type {
702
- ServerContext,
703
- ServerLoader,
704
- ApiContext,
705
- WssContext,
706
- RouteMiddleware,
707
- ApiMiddleware,
708
- GenerateStaticParams,
709
- } from "@lolyjs/core";
710
-
711
- // Validation
712
- import { validate, safeValidate, ValidationError } from "@lolyjs/core";
713
-
714
- // Security
715
- import { sanitizeString, sanitizeObject } from "@lolyjs/core";
716
- import {
717
- createRateLimiter,
718
- defaultRateLimiter,
719
- strictRateLimiter,
720
- } from "@lolyjs/core";
721
-
722
- // Logging
723
- import { logger, createModuleLogger, getRequestLogger } from "@lolyjs/core";
724
-
725
- // Client
726
- import { Link } from "@lolyjs/core/components";
727
- import { lolySocket } from "@lolyjs/core/sockets";
728
- import { revalidate, revalidatePath } from "@lolyjs/core/client-cache";
729
- ```
730
-
731
- ---
732
-
733
- ## License
734
-
735
- ISC
736
-
737
- ---
738
-
739
- ## Built With
740
-
741
- - [React](https://react.dev/) - UI library
742
- - [Express](https://expressjs.com/) - Web framework
743
- - [Rspack](https://rspack.dev/) - Fast bundler
744
- - [Socket.IO](https://socket.io/) - WebSocket library
745
- - [Pino](https://getpino.io/) - Fast logger
746
- - [Zod](https://zod.dev/) - Schema validation
747
- - [Helmet](https://helmetjs.github.io/) - Security headers
748
-
749
- ---
750
-
751
- <div align="center">
752
-
753
- **Made with ❤️ by the Loly team**
754
-
755
- </div>
1
+ # Loly Framework
2
+
3
+ <div align="center">
4
+
5
+ **A modern, full-stack React framework with native WebSocket support, route-level middlewares, and enterprise-grade features**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@lolyjs/core?style=flat-square)](https://www.npmjs.com/package/@lolyjs/core)
8
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg?style=flat-square)](https://opensource.org/licenses/ISC)
9
+ ![Downloads](https://img.shields.io/npm/dm/@lolyjs/core)
10
+ <br>
11
+ [![Alpha](https://img.shields.io/badge/status-alpha-red.svg)](https://github.com/MenvielleValen/loly-framework)
12
+ [![Expermiental](https://img.shields.io/badge/phase-experimental-black.svg)](https://github.com/MenvielleValen/loly-framework)
13
+
14
+ _Built with React 19, Express, Rspack, Socket.IO, and TypeScript_
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ ## Getting Started
21
+
22
+ Create a new Loly application in seconds:
23
+
24
+ ```bash
25
+ npx create-loly-app mi-app
26
+ ```
27
+
28
+ This will create a new project with all the necessary files and dependencies. For more information about the CLI, visit the [@lolyjs/cli package](https://www.npmjs.com/package/@lolyjs/cli).
29
+
30
+ ---
31
+
32
+ ## Overview
33
+
34
+ Loly is a full-stack React framework that combines the simplicity of file-based routing with powerful server-side rendering, static site generation, and unique features like native WebSocket support and route-level middlewares.
35
+
36
+ ### What Makes Loly Different?
37
+
38
+ - 🔌 **Native WebSocket Support** - Built-in Socket.IO integration with automatic namespace routing
39
+ - 🎯 **Route-Level Middlewares** - Define middlewares directly in your routes for pages and APIs
40
+ - 📁 **Separation of Concerns** - Server logic in `page.server.hook.ts` and `layout.server.hook.ts` separate from React components
41
+ - 🚀 **Hybrid Rendering** - SSR, SSG, and CSR with streaming support
42
+ - 🛡️ **Security First** - Built-in rate limiting, validation, sanitization, and security headers
43
+ - ⚡ **Performance** - Fast bundling with Rspack and optimized code splitting
44
+
45
+ ---
46
+
47
+ ## Quick Start
48
+
49
+ ### Installation
50
+
51
+ ```bash
52
+ npm install @lolyjs/core react react-dom
53
+ # or
54
+ pnpm add @lolyjs/core react react-dom
55
+ ```
56
+
57
+ ### Create Your First Page
58
+
59
+ ```tsx
60
+ // app/page.tsx
61
+ export default function Home() {
62
+ return <h1>Hello, Loly!</h1>;
63
+ }
64
+ ```
65
+
66
+ ### Add Server-Side Data
67
+
68
+ ```tsx
69
+ // app/page.server.hook.ts (preferred) or app/server.hook.ts (legacy)
70
+ import type { ServerLoader } from "@lolyjs/core";
71
+
72
+ export const getServerSideProps: ServerLoader = async (ctx) => {
73
+ const data = await fetchData();
74
+
75
+ return {
76
+ props: { data },
77
+ metadata: {
78
+ title: "Home Page",
79
+ description: "Welcome to Loly",
80
+ // See "SEO & Metadata" section below for full metadata options
81
+ },
82
+ };
83
+ };
84
+ ```
85
+
86
+ ```tsx
87
+ // app/page.tsx
88
+ export default function Home({ props }) {
89
+ return <h1>{props.data}</h1>;
90
+ }
91
+ ```
92
+
93
+ ### Start Development Server
94
+
95
+ ```bash
96
+ npx loly dev
97
+ # Server runs on http://localhost:3000
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Key Features
103
+
104
+ ### 🔌 Native WebSocket Support
105
+
106
+ Loly includes built-in WebSocket support with automatic namespace routing. Define WebSocket events using the same file-based routing pattern as pages and APIs:
107
+
108
+ ```tsx
109
+ // app/wss/chat/events.ts
110
+ import type { WssContext } from "@lolyjs/core";
111
+
112
+ export const events = [
113
+ {
114
+ name: "connection",
115
+ handler: (ctx: WssContext) => {
116
+ console.log("Client connected:", ctx.socket.id);
117
+ },
118
+ },
119
+ {
120
+ name: "message",
121
+ handler: (ctx: WssContext) => {
122
+ const { data, actions } = ctx;
123
+ // Broadcast to all clients
124
+ actions.broadcast("message", {
125
+ text: data.text,
126
+ from: ctx.socket.id,
127
+ });
128
+ },
129
+ },
130
+ ];
131
+ ```
132
+
133
+ **Client-side:**
134
+
135
+ ```tsx
136
+ import { lolySocket } from "@lolyjs/core/sockets";
137
+
138
+ const socket = lolySocket("/chat");
139
+
140
+ socket.on("message", (data) => {
141
+ console.log("Received:", data);
142
+ });
143
+
144
+ socket.emit("message", { text: "Hello!" });
145
+ ```
146
+
147
+ **Key Benefits:**
148
+
149
+ - Automatic namespace creation from file structure
150
+ - Same routing pattern as pages and APIs
151
+ - Built-in broadcasting helpers (`emit`, `broadcast`, `emitTo`, `emitToClient`)
152
+ - No manual configuration required
153
+
154
+ ### 🎯 Route-Level Middlewares
155
+
156
+ Define middlewares directly in your routes for fine-grained control. Middlewares run before `getServerSideProps` (pages) or API handlers and can modify `ctx.locals`, set headers, redirect, etc.
157
+
158
+ **For Pages:**
159
+
160
+ ```tsx
161
+ // app/dashboard/page.server.hook.ts (preferred) or app/dashboard/server.hook.ts (legacy)
162
+ import type { RouteMiddleware, ServerLoader } from "@lolyjs/core";
163
+
164
+ export const beforeServerData: RouteMiddleware[] = [
165
+ async (ctx, next) => {
166
+ // Authentication
167
+ const token = ctx.req.headers.authorization;
168
+ if (!token) {
169
+ ctx.res.redirect("/login");
170
+ return; // Don't call next() if redirecting
171
+ }
172
+ ctx.locals.user = await verifyToken(token);
173
+ await next(); // Call next() to continue to next middleware or getServerSideProps
174
+ },
175
+ ];
176
+
177
+ export const getServerSideProps: ServerLoader = async (ctx) => {
178
+ const user = ctx.locals.user; // Available from middleware
179
+ return { props: { user } };
180
+ };
181
+ ```
182
+
183
+ **For API Routes:**
184
+
185
+ ```tsx
186
+ // app/api/protected/route.ts
187
+ import type { ApiMiddleware, ApiContext } from "@lolyjs/core";
188
+
189
+ // Global middleware for all methods (GET, POST, PUT, etc.)
190
+ export const beforeApi: ApiMiddleware[] = [
191
+ async (ctx, next) => {
192
+ // Authentication
193
+ const user = await getUser(ctx.req);
194
+ if (!user) {
195
+ return ctx.Response({ error: "Unauthorized" }, 401);
196
+ }
197
+ ctx.locals.user = user;
198
+ await next();
199
+ },
200
+ ];
201
+
202
+ // Method-specific middleware (only runs before POST)
203
+ export const beforePOST: ApiMiddleware[] = [
204
+ async (ctx, next) => {
205
+ // Validation specific to POST
206
+ await next();
207
+ },
208
+ ];
209
+
210
+ // Method-specific middleware (only runs before GET)
211
+ export const beforeGET: ApiMiddleware[] = [
212
+ async (ctx, next) => {
213
+ // Cache logic specific to GET
214
+ await next();
215
+ },
216
+ ];
217
+
218
+ export async function GET(ctx: ApiContext) {
219
+ const user = ctx.locals.user;
220
+ return ctx.Response({ user });
221
+ }
222
+
223
+ export async function POST(ctx: ApiContext) {
224
+ const user = ctx.locals.user;
225
+ const data = ctx.req.body;
226
+ return ctx.Response({ created: true }, 201);
227
+ }
228
+ ```
229
+
230
+ **Key Benefits:**
231
+
232
+ - Middlewares execute before loaders/handlers
233
+ - Share data via `ctx.locals`
234
+ - Method-specific middlewares for APIs
235
+ - Clean separation of concerns
236
+
237
+ ### 📁 File-Based Routing
238
+
239
+ Routes are automatically created from your file structure:
240
+
241
+ | File Path | Route |
242
+ | ----------------------------- | --------------------- |
243
+ | `app/page.tsx` | `/` |
244
+ | `app/about/page.tsx` | `/about` |
245
+ | `app/blog/[slug]/page.tsx` | `/blog/:slug` |
246
+ | `app/post/[...path]/page.tsx` | `/post/*` (catch-all) |
247
+
248
+ **Nested Layouts:**
249
+
250
+ **⚠️ Important**: Layouts should NOT include `<html>` or `<body>` tags. The framework automatically handles the base HTML structure. Layouts should only contain content that goes inside the body.
251
+
252
+ ```tsx
253
+ // app/layout.tsx (Root layout)
254
+ export default function RootLayout({ children, appName, navigation }) {
255
+ return (
256
+ <div>
257
+ <nav>{navigation}</nav>
258
+ {children}
259
+ <footer>{appName}</footer>
260
+ </div>
261
+ );
262
+ }
263
+ ```
264
+
265
+ ```tsx
266
+ // app/layout.server.hook.ts (Root layout server hook - same directory as layout.tsx)
267
+ import type { ServerLoader } from "@lolyjs/core";
268
+
269
+ export const getServerSideProps: ServerLoader = async (ctx) => {
270
+ return {
271
+ props: {
272
+ appName: "My App",
273
+ navigation: ["Home", "About", "Blog"],
274
+ },
275
+ };
276
+ };
277
+ ```
278
+
279
+ ```tsx
280
+ // app/blog/layout.tsx (Nested layout)
281
+ export default function BlogLayout({ children, sectionTitle }) {
282
+ return (
283
+ <div>
284
+ <h1>{sectionTitle}</h1>
285
+ <aside>Sidebar</aside>
286
+ <main>{children}</main>
287
+ </div>
288
+ );
289
+ }
290
+ ```
291
+
292
+ ```tsx
293
+ // app/blog/layout.server.hook.ts (Nested layout server hook - same directory as layout.tsx)
294
+ import type { ServerLoader } from "@lolyjs/core";
295
+
296
+ export const getServerSideProps: ServerLoader = async (ctx) => {
297
+ return {
298
+ props: {
299
+ sectionTitle: "Blog Section",
300
+ },
301
+ };
302
+ };
303
+ ```
304
+
305
+ **Layout Server Hooks:**
306
+
307
+ Layouts can have their own server hooks that provide stable data across all pages. Props from layout server hooks are automatically merged with page props:
308
+
309
+ - **Layout props** (from `layout.server.hook.ts`) are stable and available to both the layout and all pages
310
+ - **Page props** (from `page.server.hook.ts`) are specific to each page and override layout props if there's a conflict
311
+ - **Combined props** are available to both layouts and pages
312
+
313
+ **File Convention:**
314
+ - Layout server hooks: `app/layout.server.hook.ts` (same directory as `layout.tsx`)
315
+ - Page server hooks: `app/page.server.hook.ts` (preferred) or `app/server.hook.ts` (legacy, backward compatible)
316
+
317
+ ### 🚀 Hybrid Rendering
318
+
319
+ Choose the best rendering strategy for each page:
320
+
321
+ **SSR (Server-Side Rendering):**
322
+
323
+ ```tsx
324
+ // app/posts/page.server.hook.ts (preferred) or app/posts/server.hook.ts (legacy)
325
+ export const dynamic = "force-dynamic" as const;
326
+
327
+ export const getServerSideProps: ServerLoader = async (ctx) => {
328
+ const posts = await fetchFreshPosts();
329
+ return { props: { posts } };
330
+ };
331
+ ```
332
+
333
+ **SSG (Static Site Generation):**
334
+
335
+ ```tsx
336
+ // app/blog/[slug]/page.server.hook.ts (preferred) or app/blog/[slug]/server.hook.ts (legacy)
337
+ export const dynamic = "force-static" as const;
338
+
339
+ export const generateStaticParams: GenerateStaticParams = async () => {
340
+ const posts = await getAllPosts();
341
+ return posts.map((post) => ({ slug: post.slug }));
342
+ };
343
+
344
+ export const getServerSideProps: ServerLoader = async (ctx) => {
345
+ const post = await getPost(ctx.params.slug);
346
+ return { props: { post } };
347
+ };
348
+ ```
349
+
350
+ **CSR (Client-Side Rendering):**
351
+
352
+ ```tsx
353
+ // app/dashboard/page.tsx (No page.server.hook.ts)
354
+ import { useState, useEffect } from "react";
355
+
356
+ export default function Dashboard() {
357
+ const [data, setData] = useState(null);
358
+
359
+ useEffect(() => {
360
+ fetchData().then(setData);
361
+ }, []);
362
+
363
+ return <div>{data}</div>;
364
+ }
365
+ ```
366
+
367
+ ### 🔌 API Routes
368
+
369
+ Create RESTful APIs with flexible middleware support:
370
+
371
+ ```tsx
372
+ // app/api/posts/route.ts
373
+ import type { ApiContext } from "@lolyjs/core";
374
+ import { validate } from "@lolyjs/core";
375
+ import { z } from "zod";
376
+
377
+ const postSchema = z.object({
378
+ title: z.string().min(1),
379
+ content: z.string().min(1),
380
+ });
381
+
382
+ export async function GET(ctx: ApiContext) {
383
+ const posts = await getPosts();
384
+ return ctx.Response({ posts });
385
+ }
386
+
387
+ export async function POST(ctx: ApiContext) {
388
+ const data = validate(postSchema, ctx.req.body);
389
+ const post = await createPost(data);
390
+ return ctx.Response({ post }, 201);
391
+ }
392
+ ```
393
+
394
+ ### 📊 SEO & Metadata
395
+
396
+ Loly provides comprehensive metadata support for SEO and social sharing. Metadata can be defined at both layout and page levels, with intelligent merging:
397
+
398
+ **Layout Metadata (Base/Defaults):**
399
+
400
+ ```tsx
401
+ // app/layout.server.hook.ts
402
+ import type { ServerLoader } from "@lolyjs/core";
403
+
404
+ export const getServerSideProps: ServerLoader = async () => {
405
+ return {
406
+ props: { /* ... */ },
407
+ metadata: {
408
+ // Site-wide defaults
409
+ description: "My awesome site",
410
+ lang: "en",
411
+ robots: "index, follow",
412
+ themeColor: "#000000",
413
+
414
+ // Open Graph defaults
415
+ openGraph: {
416
+ type: "website",
417
+ siteName: "My Site",
418
+ locale: "en_US",
419
+ },
420
+
421
+ // Twitter Card defaults
422
+ twitter: {
423
+ card: "summary_large_image",
424
+ },
425
+
426
+ // Custom meta tags
427
+ metaTags: [
428
+ { name: "author", content: "My Name" },
429
+ ],
430
+
431
+ // Custom link tags (preconnect, etc.)
432
+ links: [
433
+ { rel: "preconnect", href: "https://api.example.com" },
434
+ ],
435
+ },
436
+ };
437
+ };
438
+ ```
439
+
440
+ **Page Metadata (Overrides Layout):**
441
+
442
+ ```tsx
443
+ // app/page.server.hook.ts
444
+ import type { ServerLoader } from "@lolyjs/core";
445
+
446
+ export const getServerSideProps: ServerLoader = async (ctx) => {
447
+ const post = await getPost(ctx.params.slug);
448
+
449
+ return {
450
+ props: { post },
451
+ metadata: {
452
+ // Page-specific (overrides layout)
453
+ title: `${post.title} | My Site`,
454
+ description: post.excerpt,
455
+ canonical: `https://mysite.com/blog/${post.slug}`,
456
+
457
+ // Open Graph (inherits type, siteName from layout)
458
+ openGraph: {
459
+ title: post.title,
460
+ description: post.excerpt,
461
+ url: `https://mysite.com/blog/${post.slug}`,
462
+ image: {
463
+ url: post.imageUrl,
464
+ width: 1200,
465
+ height: 630,
466
+ alt: post.title,
467
+ },
468
+ },
469
+
470
+ // Twitter Card (inherits card type from layout)
471
+ twitter: {
472
+ title: post.title,
473
+ description: post.excerpt,
474
+ image: post.imageUrl,
475
+ imageAlt: post.title,
476
+ },
477
+ },
478
+ };
479
+ };
480
+ ```
481
+
482
+ **Full Metadata API:**
483
+
484
+ ```tsx
485
+ interface PageMetadata {
486
+ // Basic fields
487
+ title?: string;
488
+ description?: string;
489
+ lang?: string;
490
+ canonical?: string;
491
+ robots?: string;
492
+ themeColor?: string;
493
+ viewport?: string;
494
+
495
+ // Open Graph
496
+ openGraph?: {
497
+ title?: string;
498
+ description?: string;
499
+ type?: string;
500
+ url?: string;
501
+ image?: string | {
502
+ url: string;
503
+ width?: number;
504
+ height?: number;
505
+ alt?: string;
506
+ };
507
+ siteName?: string;
508
+ locale?: string;
509
+ };
510
+
511
+ // Twitter Cards
512
+ twitter?: {
513
+ card?: "summary" | "summary_large_image" | "app" | "player";
514
+ title?: string;
515
+ description?: string;
516
+ image?: string;
517
+ imageAlt?: string;
518
+ site?: string;
519
+ creator?: string;
520
+ };
521
+
522
+ // Custom meta tags
523
+ metaTags?: Array<{
524
+ name?: string;
525
+ property?: string;
526
+ httpEquiv?: string;
527
+ content: string;
528
+ }>;
529
+
530
+ // Custom link tags
531
+ links?: Array<{
532
+ rel: string;
533
+ href: string;
534
+ as?: string;
535
+ crossorigin?: string;
536
+ type?: string;
537
+ }>;
538
+ }
539
+ ```
540
+
541
+ **Key Features:**
542
+
543
+ - **Layout + Page Merging**: Layout metadata provides defaults, page metadata overrides specific fields
544
+ - **Automatic Updates**: Metadata updates automatically during SPA navigation
545
+ - **SSR & SSG Support**: Works in both server-side rendering and static generation
546
+ - **Type-Safe**: Full TypeScript support with `PageMetadata` type
547
+
548
+ ### 🛡️ Built-in Security
549
+
550
+ **Rate Limiting:**
551
+
552
+ ```tsx
553
+ // loly.config.ts
554
+ import { ServerConfig } from "@lolyjs/core";
555
+
556
+ export const config = (env: string): ServerConfig => {
557
+ return {
558
+ rateLimit: {
559
+ windowMs: 15 * 60 * 1000, // 15 minutes
560
+ max: 1000,
561
+ strictMax: 5,
562
+ strictPatterns: ["/api/auth/**"],
563
+ },
564
+ };
565
+ };
566
+ ```
567
+
568
+ **Validation with Zod:**
569
+
570
+ ```tsx
571
+ import { validate, ValidationError } from "@lolyjs/core";
572
+ import { z } from "zod";
573
+
574
+ const schema = z.object({
575
+ email: z.string().email(),
576
+ age: z.number().int().min(0).max(150),
577
+ });
578
+
579
+ try {
580
+ const data = validate(schema, req.body);
581
+ } catch (error) {
582
+ if (error instanceof ValidationError) {
583
+ return Response({ errors: error.format() }, 400);
584
+ }
585
+ }
586
+ ```
587
+
588
+ **Automatic Sanitization:**
589
+
590
+ Route parameters and query strings are automatically sanitized to prevent XSS attacks.
591
+
592
+ **Security Headers:**
593
+
594
+ Helmet is configured by default with CSP (Content Security Policy) and nonce support.
595
+
596
+ ### 📝 Structured Logging
597
+
598
+ ```tsx
599
+ import { getRequestLogger, createModuleLogger } from "@lolyjs/core";
600
+
601
+ // Request logger (automatic request ID)
602
+ export const getServerSideProps: ServerLoader = async (ctx) => {
603
+ const logger = getRequestLogger(ctx.req);
604
+ logger.info("Processing request", { userId: ctx.locals.user?.id });
605
+ return { props: {} };
606
+ };
607
+
608
+ // Module logger
609
+ const logger = createModuleLogger("my-module");
610
+ logger.info("Module initialized");
611
+ logger.error("Error occurred", error);
612
+ ```
613
+
614
+ ---
615
+
616
+ ## Project Structure
617
+
618
+ ```
619
+ your-app/
620
+ ├── app/
621
+ │ ├── layout.tsx # Root layout
622
+ │ ├── layout.server.hook.ts # Root layout server hook (stable props)
623
+ │ ├── page.tsx # Home page (/)
624
+ │ ├── page.server.hook.ts # Page server hook (preferred) or server.hook.ts (legacy)
625
+ │ ├── _not-found.tsx # Custom 404
626
+ │ ├── _error.tsx # Custom error page
627
+ │ ├── blog/
628
+ │ │ ├── layout.tsx # Blog layout
629
+ │ │ ├── layout.server.hook.ts # Blog layout server hook
630
+ │ │ ├── page.tsx # /blog
631
+ │ │ └── [slug]/
632
+ │ │ ├── page.tsx # /blog/:slug
633
+ │ │ └── page.server.hook.ts # Page server hook
634
+ │ ├── api/
635
+ │ │ └── posts/
636
+ │ │ └── route.ts # /api/posts
637
+ │ └── wss/
638
+ │ └── chat/
639
+ │ └── events.ts # WebSocket namespace /chat
640
+ ├── components/ # React components
641
+ ├── lib/ # Utilities
642
+ ├── public/ # Static files
643
+ ├── loly.config.ts # Framework configuration
644
+ ├── init.server.ts # Server initialization (DB, services, etc.)
645
+ └── package.json
646
+ ```
647
+
648
+ ---
649
+
650
+ ## API Reference
651
+
652
+ ### Server Loader
653
+
654
+ **Page Server Hook:**
655
+
656
+ ```tsx
657
+ // app/page.server.hook.ts (preferred) or app/server.hook.ts (legacy)
658
+ import type { ServerLoader } from "@lolyjs/core";
659
+
660
+ export const getServerSideProps: ServerLoader = async (ctx) => {
661
+ const { req, res, params, pathname, locals } = ctx;
662
+
663
+ // Fetch data
664
+ const data = await fetchData();
665
+
666
+ // Redirect
667
+ return {
668
+ redirect: {
669
+ destination: "/new-path",
670
+ permanent: true,
671
+ },
672
+ };
673
+
674
+ // Not found
675
+ return { notFound: true };
676
+
677
+ // Return props
678
+ return {
679
+ props: { data },
680
+ metadata: {
681
+ title: "Page Title",
682
+ description: "Page description",
683
+ // See "SEO & Metadata" section above for full metadata options
684
+ // including Open Graph, Twitter Cards, canonical URLs, etc.
685
+ },
686
+ };
687
+ };
688
+ ```
689
+
690
+ **Layout Server Hook:**
691
+
692
+ ```tsx
693
+ // app/layout.server.hook.ts (same directory as layout.tsx)
694
+ import type { ServerLoader } from "@lolyjs/core";
695
+
696
+ export const getServerSideProps: ServerLoader = async (ctx) => {
697
+ // Fetch stable data that persists across all pages
698
+ const user = await getCurrentUser();
699
+ const navigation = await getNavigation();
700
+
701
+ return {
702
+ props: {
703
+ user, // Available to layout and all pages
704
+ navigation, // Available to layout and all pages
705
+ },
706
+ };
707
+ };
708
+ ```
709
+
710
+ **Props Merging:**
711
+
712
+ - Layout props (from `layout.server.hook.ts`) are merged first
713
+ - Page props (from `page.server.hook.ts`) are merged second and override layout props
714
+ - Both layouts and pages receive the combined props
715
+
716
+ ```tsx
717
+ // app/layout.tsx
718
+ export default function Layout({ user, navigation, children }) {
719
+ // Receives: user, navigation (from layout.server.hook.ts)
720
+ // Also receives: any props from page.server.hook.ts
721
+ return <div>{/* ... */}</div>;
722
+ }
723
+
724
+ // app/page.tsx
725
+ export default function Page({ user, navigation, posts }) {
726
+ // Receives: user, navigation (from layout.server.hook.ts)
727
+ // Receives: posts (from page.server.hook.ts)
728
+ return <div>{/* ... */}</div>;
729
+ }
730
+ ```
731
+
732
+ ### API Route Handler
733
+
734
+ ```tsx
735
+ import type { ApiContext } from "@lolyjs/core";
736
+
737
+ export async function GET(ctx: ApiContext) {
738
+ return ctx.Response({ data: "value" });
739
+ }
740
+
741
+ export async function POST(ctx: ApiContext) {
742
+ return ctx.Response({ created: true }, 201);
743
+ }
744
+
745
+ export async function DELETE(ctx: ApiContext) {
746
+ return ctx.Response({ deleted: true }, 204);
747
+ }
748
+ ```
749
+
750
+ ### WebSocket Event Handler
751
+
752
+ ```tsx
753
+ import type { WssContext } from "@lolyjs/core";
754
+
755
+ export const events = [
756
+ {
757
+ name: "connection",
758
+ handler: (ctx: WssContext) => {
759
+ // Handle connection
760
+ },
761
+ },
762
+ {
763
+ name: "custom-event",
764
+ handler: (ctx: WssContext) => {
765
+ const { socket, data, actions } = ctx;
766
+
767
+ // Emit to all clients
768
+ actions.emit("response", { message: "Hello" });
769
+
770
+ // Broadcast to all except sender
771
+ actions.broadcast("notification", data);
772
+
773
+ // Emit to specific socket
774
+ actions.emitTo(socketId, "private", data);
775
+ },
776
+ },
777
+ ];
778
+ ```
779
+
780
+ ### Client Cache
781
+
782
+ ```tsx
783
+ import { revalidate } from "@lolyjs/core/client-cache";
784
+
785
+ export default function Page({ props }) {
786
+ const handleRefresh = async () => {
787
+ await revalidate(); // Refresh current page data
788
+ };
789
+
790
+ return <div>{/* Your UI */}</div>;
791
+ }
792
+ ```
793
+
794
+ ### Components
795
+
796
+ ```tsx
797
+ import { Link } from "@lolyjs/core/components";
798
+
799
+ export default function Navigation() {
800
+ return (
801
+ <nav>
802
+ <Link href="/">Home</Link>
803
+ <Link href="/about">About</Link>
804
+ <Link href="/blog/[slug]" params={{ slug: "my-post" }}>
805
+ My Post
806
+ </Link>
807
+ </nav>
808
+ );
809
+ }
810
+ ```
811
+
812
+ ---
813
+
814
+ ## Configuration
815
+
816
+ ### Framework Configuration
817
+
818
+ Create `loly.config.ts` in your project root to configure the framework:
819
+
820
+ ```tsx
821
+ import { FrameworkConfig } from "@lolyjs/core";
822
+
823
+ export default {
824
+ directories: {
825
+ app: "app",
826
+ build: ".loly",
827
+ static: "public",
828
+ },
829
+ server: {
830
+ port: 3000,
831
+ host: "localhost",
832
+ },
833
+ routing: {
834
+ trailingSlash: "ignore",
835
+ caseSensitive: false,
836
+ basePath: "",
837
+ },
838
+ rendering: {
839
+ framework: "react",
840
+ streaming: true,
841
+ ssr: true,
842
+ ssg: true,
843
+ },
844
+ } satisfies FrameworkConfig;
845
+ ```
846
+
847
+ ### Server Configuration
848
+
849
+ Configure server settings (CORS, rate limiting, etc.) in `loly.config.ts` by exporting a `config` function:
850
+
851
+ ```tsx
852
+ // loly.config.ts
853
+ import { ServerConfig } from "@lolyjs/core";
854
+
855
+ export const config = (env: string): ServerConfig => {
856
+ return {
857
+ bodyLimit: "1mb",
858
+ corsOrigin: env === "production" ? ["https://yourdomain.com"] : "*",
859
+ rateLimit: {
860
+ windowMs: 15 * 60 * 1000,
861
+ max: 1000,
862
+ strictMax: 5,
863
+ strictPatterns: ["/api/auth/**"],
864
+ },
865
+ };
866
+ };
867
+ ```
868
+
869
+ ### Server Initialization
870
+
871
+ Create `init.server.ts` in your project root to initialize services when Express starts (database connections, external services, etc.):
872
+
873
+ ```tsx
874
+ // init.server.ts
875
+ import { InitServerData } from "@lolyjs/core";
876
+
877
+ export async function init({
878
+ serverContext,
879
+ }: {
880
+ serverContext: InitServerData;
881
+ }) {
882
+ // Initialize database connection
883
+ await connectToDatabase();
884
+
885
+ // Setup external services
886
+ await setupExternalServices();
887
+
888
+ // Any other initialization logic
889
+ console.log("Server initialized successfully");
890
+ }
891
+ ```
892
+
893
+ **Note**: `init.server.ts` is for initializing your application services, not for configuring Loly Framework. Framework configuration goes in `loly.config.ts`.
894
+
895
+ ---
896
+
897
+ ## CLI Commands
898
+
899
+ ```bash
900
+ # Development server
901
+ npx loly dev
902
+
903
+ # Build for production
904
+ npx loly build
905
+
906
+ # Start production server
907
+ npx loly start
908
+ ```
909
+
910
+ ---
911
+
912
+ ## TypeScript Support
913
+
914
+ Loly is built with TypeScript and provides full type safety:
915
+
916
+ ```tsx
917
+ import type {
918
+ ServerContext,
919
+ ServerLoader,
920
+ ApiContext,
921
+ WssContext,
922
+ RouteMiddleware,
923
+ ApiMiddleware,
924
+ GenerateStaticParams,
925
+ } from "@lolyjs/core";
926
+ ```
927
+
928
+ ---
929
+
930
+ ## Production
931
+
932
+ ### Build
933
+
934
+ ```bash
935
+ npm run build
936
+ ```
937
+
938
+ This generates:
939
+
940
+ - Client bundle (`.loly/client`)
941
+ - Static pages if using SSG (`.loly/ssg`)
942
+ - Server code (`.loly/server`)
943
+
944
+ ### Environment Variables
945
+
946
+ ```bash
947
+ PORT=3000
948
+ HOST=0.0.0.0
949
+ NODE_ENV=production
950
+ PUBLIC_WS_BASE_URL=http://localhost:3000
951
+ ```
952
+
953
+ ---
954
+
955
+ ## Exports
956
+
957
+ ```tsx
958
+ // Server
959
+ import { startDevServer, startProdServer, buildApp } from "@lolyjs/core";
960
+
961
+ // Types
962
+ import type {
963
+ ServerContext,
964
+ ServerLoader,
965
+ ApiContext,
966
+ WssContext,
967
+ RouteMiddleware,
968
+ ApiMiddleware,
969
+ GenerateStaticParams,
970
+ } from "@lolyjs/core";
971
+
972
+ // Validation
973
+ import { validate, safeValidate, ValidationError } from "@lolyjs/core";
974
+
975
+ // Security
976
+ import { sanitizeString, sanitizeObject } from "@lolyjs/core";
977
+ import {
978
+ createRateLimiter,
979
+ defaultRateLimiter,
980
+ strictRateLimiter,
981
+ } from "@lolyjs/core";
982
+
983
+ // Logging
984
+ import { logger, createModuleLogger, getRequestLogger } from "@lolyjs/core";
985
+
986
+ // Client
987
+ import { Link } from "@lolyjs/core/components";
988
+ import { lolySocket } from "@lolyjs/core/sockets";
989
+ import { revalidate, revalidatePath } from "@lolyjs/core/client-cache";
990
+ ```
991
+
992
+ ---
993
+
994
+ ## License
995
+
996
+ ISC
997
+
998
+ ---
999
+
1000
+ ## Built With
1001
+
1002
+ - [React](https://react.dev/) - UI library
1003
+ - [Express](https://expressjs.com/) - Web framework
1004
+ - [Rspack](https://rspack.dev/) - Fast bundler
1005
+ - [Socket.IO](https://socket.io/) - WebSocket library
1006
+ - [Pino](https://getpino.io/) - Fast logger
1007
+ - [Zod](https://zod.dev/) - Schema validation
1008
+ - [Helmet](https://helmetjs.github.io/) - Security headers
1009
+
1010
+ ---
1011
+
1012
+ <div align="center">
1013
+
1014
+ **Made with ❤️ by the Loly team**
1015
+
1016
+ </div>