|
1 | | -# effector/vike-react-template |
| 1 | +# Effector SSR Vike Application Template |
| 2 | + |
| 3 | +This project is a template for quickly starting a Server-Side Rendering (SSR) Node.js application, combining a powerful set of vite, react, effector, and fastify. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- **File-system routing** implemented by Vike; |
| 8 | +- **Effector** to cover state management cases; |
| 9 | +- **React.js** to use the giant ecosystem of UI components; |
| 10 | + |
| 11 | +## Used Tools |
| 12 | + |
| 13 | +- **TypeScript**: v5 with [ts-node](https://npmjs.com/ts-node) |
| 14 | +- **React**: v18 |
| 15 | +- **Vite**: v5 with [vite-plugin-svgr](https://npmjs.com/vite-plugin-svgr), [@vitejs/plugin-react](https://npmjs.com/@vitejs/plugin-react) |
| 16 | +- **Vike**: v0.4 with [vike-react](https://npmjs.com/vike-react) and few hooks in ./renderer |
| 17 | +- **Fastify**: v4 with [cookie](https://npmjs.com/cookie), [cors](https://npmjs.com/cors), [early-hints](https://npmjs.com/early-hints), [helmet](https://npmjs.com/helmet), [rate-limit](https://npmjs.com/rate-limit), more. |
| 18 | +- **Effector**: v23 with [patronum](https://npmjs.com/patronum), [reflect](https://npmjs.com/@effector/reflect), [factorio](https://npmjs.com/effector-factorio) |
| 19 | +- **Zod**: v3 |
| 20 | +- **SVGR**: as [vite-plugin-svgr](https://npmjs.com/vite-plugin-svgr) v4 |
| 21 | +- **Prettier**: v3 with [plugin-sort-imports](@trivago/prettier-plugin-sort-imports) by trivago |
| 22 | + |
| 23 | +## Requirements |
| 24 | + |
| 25 | +- Node.js 18.6+ |
| 26 | +- corepack enabled (but can be used without) |
| 27 | + |
| 28 | +## Use this template |
| 29 | + |
| 30 | +0. Press "Use this template" to create a copy of repository. |
| 31 | + |
| 32 | +1. Clone your repository: |
| 33 | + |
| 34 | +```bash |
| 35 | +git clone https://github.com/effector/vike-react-template my-app |
| 36 | +# OR |
| 37 | +gh repo clone effector/vike-react-template my-app |
| 38 | +``` |
| 39 | + |
| 40 | +2. Navigate to the project directory: |
| 41 | + |
| 42 | +```bash |
| 43 | +cd my-app |
| 44 | +``` |
| 45 | + |
| 46 | +3. Install package manager and dependencies: |
| 47 | + |
| 48 | +```bash |
| 49 | +corepack enable |
| 50 | +corepack prepare |
| 51 | +pnpm install |
| 52 | +``` |
| 53 | + |
| 54 | +## Usage |
| 55 | + |
| 56 | +1. Run in development mode: |
| 57 | + |
| 58 | +```bash |
| 59 | +pnpm dev |
| 60 | +``` |
| 61 | + |
| 62 | +2. Build for production: |
| 63 | + |
| 64 | +```bash |
| 65 | +pnpm build |
| 66 | +``` |
| 67 | + |
| 68 | +3. Run production version: |
| 69 | + |
| 70 | +```bash |
| 71 | +pnpm start |
| 72 | +``` |
| 73 | + |
| 74 | +## Project Structure |
| 75 | + |
| 76 | +Strongly recommend to carefully **review each file** in this repository, before using it in production. |
| 77 | + |
| 78 | +This project inherites [Vike project structure](https://vike.dev/file-structure): |
| 79 | + |
| 80 | + dist/ |
| 81 | + pages/ |
| 82 | + public/ |
| 83 | + renderer/ |
| 84 | + server/ |
| 85 | + src/ |
| 86 | + |
| 87 | +- `dist` contains result of `pnpm build`, it is production build; |
| 88 | +- `pages` is a Vike's [filesystem routing](https://vike.dev/routing#filesystem-routing); |
| 89 | +- `public` is a [static files directory](https://vike.dev/static-directory#public); |
| 90 | +- `renderer` is a react + effector [integration hooks](https://vike.dev/file-structure#renderer); |
| 91 | +- `server` is a fastify server, builds with `tsc`, runs with `ts-node`; |
| 92 | +- `src` is a [FSD](https://feature-sliced.design) basis, with code imported into `pages`, and `renderer`; |
| 93 | + |
| 94 | +### `pages/` |
| 95 | + |
| 96 | +#### `pages/+Wrapper.tsx` |
| 97 | + |
| 98 | +It is a data provider for logic uses effector. |
| 99 | + |
| 100 | +#### `pages/+Layout.tsx` |
| 101 | + |
| 102 | +To wrap up components with some layout use [`+Layout.tsx`](https://vike.dev/Layout). |
| 103 | + |
| 104 | +Also, you can [nest multiple layouts](https://vike.dev/Layout#nested-layouts). |
| 105 | + |
| 106 | +#### `pages/index/+pageStarted.ts` |
| 107 | + |
| 108 | +This vike hook describes what event Vike should call on server when page logic can be started. |
| 109 | + |
| 110 | +Usually looks like this: |
| 111 | + |
| 112 | +```ts |
| 113 | +import { createPageStart } from "~/shared/init"; |
| 114 | + |
| 115 | +export const pageStarted = createPageStart(); |
| 116 | + |
| 117 | +// pageStarted has type: |
| 118 | +pageStarted: EventCallable<{ |
| 119 | + params: Record<string, string>; |
| 120 | + data: void; |
| 121 | +}>; |
| 122 | +``` |
| 123 | + |
| 124 | +`params` looks like `{ id: "foo" }` for route `pages/example/@id` and pathname `/example/foo`. |
| 125 | + |
| 126 | +| Url | Route | `params` | |
| 127 | +| ------------ | ----------------- | --------------- | |
| 128 | +| / | pages/index | `{}` | |
| 129 | +| /example/100 | pages/example/@id | `{ id: "100" }` | |
| 130 | + |
| 131 | +#### `pages/index/+Page.tsx` |
| 132 | + |
| 133 | +This is a page component. It can import `model.ts` and all from src using `~/` alias. |
| 134 | + |
| 135 | +Use `export default` and named functions: |
| 136 | + |
| 137 | +```tsx |
| 138 | +export default function PageHome() { |
| 139 | + return <h1>Hello World</h1>; |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +#### `pages/index/model.ts` |
| 144 | + |
| 145 | +This is a logic file written in effector. It can import `+pageStarted.ts` and all from src using `~/` alias. |
| 146 | + |
| 147 | +```ts |
| 148 | +import { createEffect, sample } from "effector"; |
| 149 | + |
| 150 | +import { pageStarted } from "./+pageStarted"; |
| 151 | + |
| 152 | +const helloFx = createEffect((name: string) => { |
| 153 | + console.info(`Hello ${name}`); |
| 154 | +}); |
| 155 | + |
| 156 | +sample({ |
| 157 | + clock: pageStarted, |
| 158 | + fn: () => "World", |
| 159 | + target: helloFx, |
| 160 | +}); |
| 161 | +``` |
| 162 | + |
| 163 | +When user opened http://localhost:3000, `pageStarted` fired, then sample with `clock: pageStarted` reacts and triggers `helloFx` with `"World"` argument. |
| 164 | + |
| 165 | +In our dynamic and event driven kind of environment, this is the powerful way to describe logic. |
| 166 | +**Without needing to deal with React**, Hooks, Rerenders, StrictMode, Next.js, etc. |
| 167 | + |
| 168 | +### `pages/example/@id` |
| 169 | + |
| 170 | +Let's talk about data loading. |
| 171 | + |
| 172 | +You can always use simple `createEffect` to load data in Browser, just react on user actions, not `pageStarted` nor `appStarted`. |
| 173 | + |
| 174 | +Until, you read [Data Fetching article](https://vike.dev/data-fetching) from Vike.dev. It will works until [Client-Side Routing](https://vike.dev/client-routing). |
| 175 | +Vike has `+data.ts` hook to fetch data on client and server navigation. |
| 176 | + |
| 177 | +> In case of refetch data using triggering some event on client side, or changing filters in user interface consider making client navigation with query parameters. |
| 178 | +
|
| 179 | +#### `pages/example/@id/+data.ts` |
| 180 | + |
| 181 | +Declare your data fetcher. Name of the exported function must be `data`. Use this as starting point: |
| 182 | + |
| 183 | +```ts |
| 184 | +import type { PageContextServer } from "vike/types"; |
| 185 | + |
| 186 | +export async function data(pageContext: PageContextServer) { |
| 187 | + const { routeParams } = pageContext; |
| 188 | + const { id } = routeParams; |
| 189 | + |
| 190 | + // await api.someItems.getById(id) |
| 191 | + |
| 192 | + return { |
| 193 | + sampleData: { id: id ?? "<empty>" }, |
| 194 | + }; |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +Consider placing all reusable API requests into `src/shared/api`. |
| 199 | +Using barrel file pattern is optional but very useful. |
| 200 | + |
| 201 | +#### `pages/example/@id/+pageStarted.ts` |
| 202 | + |
| 203 | +In case of data loading, hook pageStarted should be modified: |
| 204 | + |
| 205 | +```ts |
| 206 | +import { createPageStart } from "~/shared/init"; |
| 207 | + |
| 208 | +import type { data } from "./+data"; |
| 209 | + |
| 210 | +export const pageStarted = createPageStart<Awaited<ReturnType<typeof data>>>(); |
| 211 | +``` |
| 212 | + |
| 213 | +You need just bind resulting type of your `data` loader function. Vike passes result of `data()` call into `pageStarted` like `{ params: { id }, data }`. |
| 214 | + |
| 215 | +#### `pages/example/@id/model.ts` |
| 216 | + |
| 217 | +So, you can access data from `model` like this: |
| 218 | + |
| 219 | +```ts |
| 220 | +import { createStore, sample } from "effector"; |
| 221 | + |
| 222 | +import { pageStarted } from "./+pageStarted"; |
| 223 | + |
| 224 | +sample({ |
| 225 | + clock: pageStarted, |
| 226 | + fn: ({ data, params: { id } }) => data, |
| 227 | + target: insertHereAnyUnit, |
| 228 | +}); |
| 229 | +// you can actually use `source, filter` in sample |
| 230 | +``` |
| 231 | + |
| 232 | +### `pages/_error` |
| 233 | + |
| 234 | +Component created as `pages/_error/+Page.tsx` is used to show [error page](https://vike.dev/error-page). |
| 235 | + |
| 236 | +### `renderer/` |
| 237 | + |
| 238 | +Here described exact integration of vike, react, and effector. |
| 239 | +You may need to read this before changing: |
| 240 | + |
| 241 | +- [Vike Hooks](https://vike.dev/hooks); |
| 242 | +- [Build Your Own Framework](https://vike.dev/build-your-own-framework) on top of Vike; |
| 243 | +- Don't be shy, read source files, it has links to documentation; |
| 244 | + |
| 245 | +### `server/` |
| 246 | + |
| 247 | +#### `server/config.ts` |
| 248 | + |
| 249 | +Contains resolvers of configuration variables. |
| 250 | + |
| 251 | +```ts |
| 252 | +export const CONFIG = { |
| 253 | + get SERVER_PORT() { |
| 254 | + return Number.parseInt(globalThis.process.env.SERVER_PORT ?? "3000", 10); |
| 255 | + }, |
| 256 | +}; |
| 257 | +``` |
| 258 | + |
| 259 | +> This is the only way it works on Cloudflare Workers. If you have a better solution please [Leave an issue](https://github.com/effector/vike-react-template/issues). |
| 260 | +
|
| 261 | +#### `server/directory-root.ts` |
| 262 | + |
| 263 | +Declares project root directory, to resolve static assets from. |
| 264 | + |
| 265 | +#### `server/index.ts` |
| 266 | + |
| 267 | +Creates instance of `server/server`, handles signals SIGINT, SIGTERM. |
| 268 | + |
| 269 | +#### `server/server.ts` |
| 270 | + |
| 271 | +Creates fastify instance, configures plugins, read cookies, renders using vike/server. |
| 272 | + |
| 273 | +You can modify any part of this and any other files. Please, explore documentation before. |
| 274 | + |
| 275 | +#### `server/tsconfig.json` |
| 276 | + |
| 277 | +Used here to describe different environment for files in this directory. |
| 278 | + |
| 279 | +It builds for production with `tsc -p server`. It runs for development with `ts-node` ESM-loader. |
| 280 | + |
| 281 | +There is no hot reload in `server/` directory. Restart manually after changing these files. |
| 282 | + |
| 283 | +### `src/` |
| 284 | + |
| 285 | +Consider using [Feature-Sliced Design](https://feature-sliced.design/) structuring this directory. |
| 286 | + |
| 287 | +#### `src/vike.d.ts` |
| 288 | + |
| 289 | +Handles [`pageContext` typings](https://vike.dev/pageContext#typescript). |
| 290 | + |
| 291 | +#### `src/vite-env.d.ts` |
| 292 | + |
| 293 | +Handles [Vite client types](https://vitejs.dev/guide/features.html#client-types). |
| 294 | + |
| 295 | +## Customization |
| 296 | + |
| 297 | +> What kind of customizations needs to be described? integration with supabase? [Leave an issue](https://github.com/effector/vike-react-template/issues). |
| 298 | +
|
| 299 | +### Use different Package Manager |
| 300 | + |
| 301 | +First of all, delete `pnpm-lock.yaml`, dist, and node_modules: |
| 302 | + |
| 303 | +```bash |
| 304 | +rm pnpm-lock.yaml |
| 305 | +rm -rf node_modules dist |
| 306 | +``` |
| 307 | + |
| 308 | +#### Node.js v18.x: |
| 309 | + |
| 310 | +Set exact version into `packageManager` field of `package.json`: |
| 311 | + |
| 312 | +```json |
| 313 | +// package.json |
| 314 | +{ |
| 315 | + |
| 316 | +} |
| 317 | +``` |
| 318 | + |
| 319 | +Save and navigate to your terminal into the project directory: |
| 320 | + |
| 321 | +```bash |
| 322 | +# Enable corepack for your shell |
| 323 | +corepack enable |
| 324 | + |
| 325 | +# Install package manager for your project |
| 326 | +corepack prepare |
| 327 | + |
| 328 | +# Install dependencies |
| 329 | +npm install |
| 330 | +``` |
| 331 | + |
| 332 | +#### Node.js v20+: |
| 333 | + |
| 334 | +Node.js v20 has different version of corepack, so we can use `corepack use`. |
| 335 | + |
| 336 | +```bash |
| 337 | +# Enable corepack for your shell |
| 338 | +corepack enable |
| 339 | + |
| 340 | +# Install package manager for your project |
| 341 | +corepack use npm@latest # yarn@3 |
| 342 | + |
| 343 | +# Install dependencies |
| 344 | +npm install |
| 345 | + |
| 346 | +# Check packageManager field in package.json |
| 347 | +jq .packageManager package.json |
| 348 | +#> "[email protected]+sha256.1ee0e26fb669143425371ab8727fe4c5841640a2fd944863a8e8c28be966aca2" |
| 349 | +# It's OK |
| 350 | +``` |
| 351 | + |
| 352 | +## Supported OS and Node.js |
| 353 | + |
| 354 | +This template has been tested on: |
| 355 | + |
| 356 | +- macOS Sonoma 14.5, Node.js v20.10.0 |
| 357 | + |
| 358 | +## Contributing |
| 359 | + |
| 360 | +We welcome contributions to the project! Please read our contribution guidelines before submitting a pull request. |
| 361 | + |
| 362 | +## License |
| 363 | + |
| 364 | +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. |
0 commit comments