{"id":13683764,"url":"https://github.com/hiroppy/ssr-sample","last_synced_at":"2025-04-30T13:31:54.476Z","repository":{"id":49543755,"uuid":"142811120","full_name":"hiroppy/ssr-sample","owner":"hiroppy","description":"A minimum sample of Server-Side-Rendering, Single-Page-Application and Progressive Web App","archived":true,"fork":false,"pushed_at":"2021-10-18T21:14:37.000Z","size":6864,"stargazers_count":287,"open_issues_count":52,"forks_count":38,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-11-12T03:35:06.689Z","etag":null,"topics":["apollo","babel","graphql","loadable-components","pwa","react","redux-saga","server-side-rendering","spa","ssr","styled-components","typescript","webpack"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hiroppy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"hiroppy","patreon":"hiroppy"}},"created_at":"2018-07-30T01:46:07.000Z","updated_at":"2024-10-22T11:12:54.000Z","dependencies_parsed_at":"2022-08-19T01:31:41.310Z","dependency_job_id":null,"html_url":"https://github.com/hiroppy/ssr-sample","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiroppy%2Fssr-sample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiroppy%2Fssr-sample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiroppy%2Fssr-sample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiroppy%2Fssr-sample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hiroppy","download_url":"https://codeload.github.com/hiroppy/ssr-sample/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251712897,"owners_count":21631462,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apollo","babel","graphql","loadable-components","pwa","react","redux-saga","server-side-rendering","spa","ssr","styled-components","typescript","webpack"],"created_at":"2024-08-02T13:02:30.883Z","updated_at":"2025-04-30T13:31:53.653Z","avatar_url":"https://github.com/hiroppy.png","language":"TypeScript","funding_links":["https://github.com/sponsors/hiroppy","https://patreon.com/hiroppy"],"categories":["TypeScript"],"sub_categories":[],"readme":"# A minimum sample of Server-Side-Rendering, Single-Page-Application, and Progressive Web App\n\n## What's this project?\n\nThis project introduces how to implement SSR, SPA, and PWA.\n\n### Articles\n\nonly Japanese\n\n- [SPA + SSR + PWA の作り方とセキュリティについて](https://blog.hiroppy.me/entry/ssr-sample_2)\n- [業務で使える簡単な SSR + SPA のテンプレートを公開した](http://blog.hiroppy.me/entry/ssr-sample)\n\n## Feature\n\nThis project shows several implementations like below.\n\n- Server Side Rendering\n- Single Page Application\n- Progressive Web App\n- GraphQL\n- RESTful API\n- Security (CSP, GraphQL)\n- Testing\n- Infra like the building tools\n- Measuring performance\n\n## Libraries\n\n### Dependencies\n\n| Name                | Purpose                | CSR | SSR | Note |\n| ------------------- | ---------------------- | --- | --- | ---- |\n| react               | view                   | yes | yes |      |\n| redux               | architecure            | yes | yes |      |\n| react-router        | routing                | yes | yes |      |\n| react-helmet        | head tag               | yes | yes |      |\n| redux-saga          | side effects           | yes | yes |      |\n| styled-components   | CSS in JS              | yes | yes |      |\n| loadable-components | dynamic import         | yes | yes |      |\n| apollo-boost        | GraphQL                | yes | yes |      |\n| express             | server side framework  | N/A | yes |      |\n| nanoid              | Creating a random hash | N/A | N/A |      |\n\n### DevDependencies\n\n| Name              | Purpose                                      | Note |\n| ----------------- | -------------------------------------------- | ---- |\n| typescript        | Alt                                          |      |\n| webpack           | a bundler for client side                    |      |\n| babel             | transpile typescript and loadable-components |      |\n| storybook         | preview                                      |      |\n| storyshots        | snapshot tests                               |      |\n| jest              | test runner                                  |      |\n| testing-library   | a helper to test react                       |      |\n| nodemon           | a watcher for server side                    |      |\n| prettier          | formatter                                    |      |\n| typescript-eslint | linter                                       |      |\n| workbox           | service worker                               |      |\n| clinic            | performance profiling                        |      |\n| autocannon        | benchmarking tool                            |      |\n\n## Pages\n\nSee the router: [src/client/router/](src/client/router/).\n\nThis application has 3 pages and creates SPA based on redux and redux-saga.  \nSaga page and Apollo page use same components so you can compare each implementation.\n\n### Top\n\nThis page reads README.md using `babel-plugin-macro`.\n\nsrc: [src/client/components/pages/Top](src/client/components/pages/Top)\n\n### Saga\n\nThis page runs just redux-saga application.\n\npage src: [src/client/components/pages/Saga](src/client/components/pages/Saga)\n\n### Apollo\n\nThis page runs just apollo application.\n\npage src: [src/client/components/pages/Apollo](src/client/components/pages/Apollo)\n\n## Control SSR and SPA\n\ndesign concept: [gist](https://gist.github.com/hiroppy/9b5daf8da5cd639a62a917d536f5dfc5)  \nsrc: [src/client/sagas/pages.ts](src/client/sagas/pages.ts)\n\nAll pages fork saga processes.\n\n- `appProcess`\n  - a common processing to execute on all pages(e.g. confirming login, sending to GA, etc...)\n- pages\n  - `loadTopPage`, `loadingApolloPage`\n    - just stop saga when it ran at a server\n  - `loadSagaPage`\n    - fetching data and then stopping if it ran at a server\n\n`appProcess` and `pages` run in parallel, also they run the same code in a server and client.\n\n**Need to call `END` when running on Node.js**\n\nIf you do SSR using redux-saga, you have to stop redux-saga process when all processes are finished.\n\n```js\ntry {\n  // fetch...\n\n  yield put(success());\n} catch (err) {\n  yield put(failure(err));\n} finally {\n  if (!process.env.IS_BROWSER) {\n    yield put(END);\n  }\n}\n```\n\n## Global Variables\n\nsrc: [src/server/controllers/renderer/renderer.tsx](src/server/controllers/renderer/renderer.tsx).\n\nUse the following variables to pass data acquired by a server to the client side.\n\n### `data-json`\n\nThis script tag has state and data which are fetched via redux-saga, etc at the server.\n\n`\u003cscript id=\"initial-data\" type=\"text/plain\" data-json=...\u003e\u003c/script\u003e`.\n\n### `window.__APOLLO_STATE__`\n\nThis variable has GraphQL data which are fetched at the server.\n\n## Lighthouse\n\n![lighthouse](./assets/lighthouse.png)\n\nIf you want to get 100 point for Best Practices, you need to set a reverse proxy server like Nginx because Express hasn't implemented http/2 yet.(also Performance)\n\n## Setup\n\n```sh\n$ git clone git@github.com:hiroppy/ssr-sample.git\n$ cd ssr-sample\n$ npm i\n```\n\n## Development\n\n```sh\n$ npm start\n$ open http://localhost:3000\n\n# GraphQL Playground\n$ open http://localhost:3000/graphql\n```\n\n## Storybook\n\n```sh\n$ npm run start:storybook\n$ open http://localhost:6006\n```\n\n## Test\n\n```sh\n$ npm test\n```\n\n## Production\n\n```sh\n$ npm run build             # npm run build:client + npm run build:server\n$ npm run start:prod        # run server and use 3000\n$ open http://localhost:8080\n```\n\n## Deploy\n\n```sh\n$ npm run deploy:storybook\n```\n\n## Performance\n\n```sh\n$ npm run build\n$ npm run start:prod\n$ npm run benchmark # rps\n\nRunning 10s test @ http://localhost:8080\n100 connections\n\n┌─────────┬────────┬────────┬────────┬─────────┬───────────┬───────────┬────────────┐\n│ Stat    │ 2.5%   │ 50%    │ 97.5%  │ 99%     │ Avg       │ Stdev     │ Max        │\n├─────────┼────────┼────────┼────────┼─────────┼───────────┼───────────┼────────────┤\n│ Latency │ 161 ms │ 406 ms │ 829 ms │ 1277 ms │ 413.26 ms │ 191.69 ms │ 2649.38 ms │\n└─────────┴────────┴────────┴────────┴─────────┴───────────┴───────────┴────────────┘\n┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────┬─────────┐\n│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev  │ Min     │\n├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤\n│ Req/Sec   │ 210     │ 210     │ 233     │ 264     │ 236.6   │ 18.87  │ 210     │\n├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤\n│ Bytes/Sec │ 3.16 MB │ 3.16 MB │ 3.51 MB │ 3.98 MB │ 3.56 MB │ 284 kB │ 3.16 MB │\n└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴─────────┘\n\nReq/Bytes counts sampled once per second.\n\n$ npm run benchmark:flame # flamegraph\n```\n\n![flamegraph](./assets/flamegraph.png)\n\n## Note\n\nThis repository shows how to write and so does not introduce Atomic Design.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhiroppy%2Fssr-sample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhiroppy%2Fssr-sample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhiroppy%2Fssr-sample/lists"}