{"id":13571680,"url":"https://github.com/gpbl/isomorphic500","last_synced_at":"2025-09-28T20:30:45.690Z","repository":{"id":23609341,"uuid":"26978465","full_name":"gpbl/isomorphic500","owner":"gpbl","description":"A 500px app built with React and Fluxible with babeljs","archived":true,"fork":false,"pushed_at":"2016-05-07T20:15:51.000Z","size":16714,"stargazers_count":1345,"open_issues_count":16,"forks_count":184,"subscribers_count":48,"default_branch":"master","last_synced_at":"2024-05-02T00:54:22.197Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://isomorphic500.herokuapp.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gpbl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-11-21T21:23:42.000Z","updated_at":"2024-03-03T07:18:12.000Z","dependencies_parsed_at":"2022-08-22T01:31:22.090Z","dependency_job_id":null,"html_url":"https://github.com/gpbl/isomorphic500","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gpbl%2Fisomorphic500","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gpbl%2Fisomorphic500/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gpbl%2Fisomorphic500/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gpbl%2Fisomorphic500/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gpbl","download_url":"https://codeload.github.com/gpbl/isomorphic500/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219873877,"owners_count":16554521,"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":[],"created_at":"2024-08-01T14:01:04.890Z","updated_at":"2025-09-28T20:30:45.226Z","avatar_url":"https://github.com/gpbl.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# isomorphic500\n\n[Isomorphic500](https://isomorphic500.herokuapp.com) is a small isomorphic ([universal](https://medium.com/@mjackson/universal-javascript-4761051b7ae9)) web application featuring photos from [500px](http://500px.com).\n\nIt is built on [express](http://expressjs.com) using [React](https://facebook.github.io/react) and [Flux](https://facebook.github.io/flux) with [yahoo/fluxible](http://fluxible.io). It is developed with [webpack](http://webpack.github.io) and [react-hot-loader](http://gaearon.github.io/react-hot-loader/) and written with [babeljs](http://babeljs.io) with the help of [eslint](http://eslint.org). It supports multiple languages using [react-intl](http://formatjs.io/react/).\n\n\u003ca href=\"https://isomorphic500.herokuapp.com\"\u003e\u003cimg src=\"https://cloud.githubusercontent.com/assets/120693/7737327/95f3de1c-ff4a-11e4-86fb-e9d3cabcdedb.png\" width=\"700\"\u003e\u003c/a\u003e\n\n[![Build Status](https://travis-ci.org/gpbl/isomorphic500.svg?branch=master)](https://travis-ci.org/gpbl/isomorphic500) \u003cimg src=\"https://david-dm.org/gpbl/isomorphic500.svg\"\u003e\n\nThe intent of this project is to solidify my experience with these technologies and perhaps to inspire other developers in their journey with React and Flux. It works also as example of a javascript development environment with all the cool recent stuff :-)\n\n- see the demo on [isomorphic500.herokuapp.com](https://isomorphic500.herokuapp.com) (with source maps!)\n- clone this repo and run the server to confirm it is actually working\n- edit a react component or a css style, and see the updated app as you save your changes!\n- read on for some technical details\n\n**Get help**\nJoin the [gitter chat](https://gitter.im/gpbl/isomorphic500?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge) or the [#isomorphic500](https://reactiflux.slack.com/archives/isomorphic500) on [reactiflux](http://www.reactiflux.com) :-)\n\n**Clone this repo**\n\n**Note** This app has been tested on node 4\n\n```\ngit clone https://github.com/gpbl/isomorphic500.git\ncd isomorphic500\nnpm install\n```\n\n**Start the app**\n\n```bash\nnpm run dev\n```\n\nand open [localhost:3000](http://localhost:3000).\n\nYou can also try the built app:\n\n```bash\nnpm run build   # First, build for production\nnpm run prod    # then, run the production version\n```\n\nthen open [localhost:8080](http://localhost:8080).\n\n\u003e If you are starting the server on Windows, please read https://github.com/gpbl/isomorphic500/issues/58\n\n\n## Table of Contents\n\n* [Application structure](#application-structure)\n  * [The fluxible app](#the-fluxible-app)\n  * [Async data](#async-data)\n  * [Router](#router)\n  * [Stores](#stores)\n    * [Resource stores](#resource-stores)\n    * [List stores](#list-stores)\n    * [The HtmlHeadStore](#the-htmlheadstore)\n* [Internationalization (i18n)](#internationalization-i18n)\n  * [How the user’s locale is detected](#how-the-user’s-locale-is-detected)\n  * [Setting up react-intl](#setting-up-react-intl)\n  * [Internationalization, the flux way](#internationalization-the-flux-way)\n  * [Sending the locale to the API](#sending-the-locale-to-the-api)\n* [Development](#development)\n  * [nodemon](#nodemon)\n  * [Webpack](#webpack)\n  * [Babeljs](#babeljs)\n  * [.editorconfig](#editorconfig)\n  * [Linting](#linting)\n  * [Debugging](#debugging)\n\n## Application structure\n\n```bash\n.\n├── index.js            # Starts the express server and the webpack dev server\n├── config              # Contains the configuration for dev and prod environments\n├── nodemon.json        # Configure nodemon to watch some files\n├── src\n│   ├── app.js          # The fluxible app\n│   ├── client.js       # Entry point for the client\n│   ├── config.js       # Config loader (load the config files from /config)\n│   ├── routes.js       # Routes used by fluxible-router\n│   ├── server.js       # Start the express server and render the routes server-side\n│   │\n│   ├── actions         # Fluxible actions\n│   ├── components      # React components\n│   ├── constants       # Constants\n│   ├── containers      # Contains React containers components\n│   │   ├── ...\n│   │   ├── Html.js     # Used to render the \u003chtml\u003e document server-side\n│   │   └── Root.js     # Root component\n\n│   ├── intl            # Contains the messages for i18n\n│   ├── server          # Server-side only code\n│   │   ├── ga.js              # Google Analytics script\n│   │   ├── intl-polyfill.js   # Patch node to support `Intl` and locale-data\n│   │   ├── render.js          # Middleware to render server-side the fluxible app\n│   │   └── setLocale.js       # Middleware to detect and set the request's locale\n│   ├── services        # Fetchr services\n│   ├── stores          # Fluxible stores\n│   ├── style           # Contains the Sass files\n│   └── utils         \n│       ├── APIUtils.js            # Wrapper to superagent for communicating with 500px API\n│       ├── CookieUtils.js         # Utility to write/read cookies \n│       ├── IntlComponents.js      # Exports wrapped react-intl components\n│       ├── IntlUtils.js           # Utilities to load `Intl` and locale-data\n│       ├── connectToIntlStore.js  # Connects react-intl components with the IntlStore\n│       ├── getIntlMessage.js      # Get react-intl messages\n│       └── trackPageView.js       # Track a page view with google analitics\n├── static              \n│   ├── assets         # Static files\n│   └── dist           # Output files for webpack on production\n└── webpack\n    ├── dev.config.js  # Webpack config for development\n    ├── prod.config.js # Webpack config for building the production files\n    └── server.js      # Used to starts the webpack dev server\n\n```\n\n### The fluxible app\n\nThe [src/app](src/app) file is the core of the Fluxible application:\n\n- it configures Fluxible with [src/containers/Root.js](src/containers/Root.js) as the root component.\n- it registers the stores so they can work on the same React context\n- it adds the [fetchr plugin]((https://github.com/yahoo/fluxible-plugin-fetchr)), to share the same API requests both client and server-side\n- it makes possible to dehydrate the stores [on the server](src/server/render.js) and rehydrate them [on the client](src/client.js)\n\n### Async data\n\nI used [Fetchr](https://github.com/yahoo/fetchr) and [fluxible-plugin-fetchr](https://github.com/yahoo/fluxible-plugin-fetchr).\n[Fetchr services](src/services) run only on server and send [superagent](http://visionmedia.github.com/superagent) requests to 500px.\n\n### Router\n\nThis app uses [fluxible-router](https://github.com/yahoo/fluxible-router) for routing. Fluxible-router works pretty well in fluxible applications since it follows the flux paradigm. The [Application component](src/containers/Root.js) uses the `@handleHistory` decorator to bind the router to the app.\n\n### Stores\n\nInstead of directly listening to stores, components use fluxible's `@connectToStores` decorator: a store state is passed to components as prop. See for example the [PhotoPage](src/containers/PhotoPage.js) or the [FeaturedPage](src/containers/FeaturedPage.js).\n\n`connectToStore` can also \"consume\" store data without actually listening to any store. This is the case of [NavBar](src/components/NavBar.js) or [LocaleSwitcher](src/components/LocaleSwitcher.js).\n\n#### Resource stores\n\nWhile REST APIs usually return collections as arrays, a resource store keeps items as big object – like the [PhotoStore](src/stores/PhotoStore.js). This simplifies the progressive resource updates that may happen during the app’s life.\n\n#### List stores\n\nA list store keeps references to a resource store, as the [FeaturedStore](src/stores/FeaturedStore.js) holds the ids of the photos in [PhotoStore](src/stores/PhotoStore.js).\n\n#### The HtmlHeadStore\n\nThe [HtmlHeadStore](src/stores/HtmlHeadStore.js) is a special store used to set the `\u003chead\u003e` meta-tags in the `Html` component, during server-side rendering. It is also listened by the `Application` component to change the browser's `document.title`.\n\nThis store listens to route actions and set its content according to the current route. It also get data from other stores (e.g. the photo's title from the `PhotoStore`), or the localized messages from the `IntlStore`.\n\n## Internationalization (i18n)\n\nTo give an example on how to implement i18n in a React application, isomorphic500 supports English, [Italian](https://www.youtube.com/watch?v=9JhuOicPFZY), Portuguese and French.\n\nThis app adopts [React Intl](http://formatjs.io/react/), which is a solid library for this purpose.\n\n### How the user’s locale is detected\n\nThe app sniffs the browser's `accept-language` request header. The [locale](https://github.com/jed/locale) npm module has a nice express middleware for that. Locales are restricted to those set in the app's [config](../config).\n\nThe user may want to override the detected locale: the [LocaleSwitcher](src/components/LocaleSwitcher.js) component set a cookie when the user chooses a language. Also, we enable the `?hl` parameter in the query string to override it. Server-side, cookie and query string are detected by the [setLocale](src/server/setLocale.js) middleware.\n\n### Setting up react-intl\n\nReact-intl requires some boilerplate to work properly. Difficulties here arise mainly for two reasons:\n\n1. React Intl relies on the [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) *global* API, not always available on node.js or some browsers (e.g. Safari). Luckly there's an [Intl polyfill](https://www.npmjs.com/package/intl): on the server we can just \"require\" it – however on the browser we want to download it *only* when `Intl` is not supported.\n\n2. For each language, we need to load a set of *locale data* (used by `Intl` to format numbers and dates) and the translated strings, called *messages* (used by `react-intl`). While on node.js we can load them in memory, on the client they need to be downloaded first – and we want to download only the relevant data for the current locale.\n\n**On the server** the solution is easy: as said, the server [loads a polyfill](src/server/intl-polyfill) including both `Intl` and the locale data. For supporting the browser, we can instead rely on our technology stack, i.e. flux and webpack.\n\n**On the client**, we have to load the `Intl` polyfill and its locale data *before* rendering the app, i.e. in [client.js](src/client.js).\n\nFor this purpose, I used webpack's `require.ensure()` to split `Intl` and localized data in multiple chunks. Only after they have been downloaded, the app can be mounted. See the `loadIntlPolyfill()` and `loadLocaleData()` functions in [IntlUtils](src/utils/IntlUtils.js): they return a promise that is resolved when the webpack chunks are downloaded and `require`d.\n\nThey are used in [client.js](client.js) before mounting the app.\n\n\u003e **Important**: since `react-intl` assumes `Intl` is already in the global scope, we can't import the fluxible app (which imports react-intl in some of its components) *before* polyfilling `Intl`. That's why you see in [client.js](src/client.js) `require(\"./app\")` inside the in the `renderApp()` function, and not as `import` on the top of the file.\n\n### Internationalization, the flux way\n\nLets talk about the data that `react-intl` needs to deliver translated content. Translated messages are saved in the [intl](src/intl) directory and shared between client and server using the [IntlStore](stores/IntlStore).\n\nThis store listens to a `LOAD_INTL_SERVER` action dispatched by [IntlActionCreator](src/actions/IntlActionCreators.js). We execute this action **only server side** before rendering the `Html` component together with the usual `navigateAction`. This allows to dehydrate/rehydrate the store content.\n\nReact-intl components need to have access to the `IntlStore`. Plus, since I'm using ES6 classes, I can't adopt the react-intl `Mixin` in my components. To solve this, I wrap the `Formatted*` components and make them available from [IntlComponents](src/utils/IntlComponents.js).\n\n### Sending the locale to the API\n\nWhile this is not required by the 500px API, we can send the current locale to the API so it can deliver localized content. This is made very easy by the Fetchr services, since they expose the `req` object: see for example the [photo service](src/services/photo.js).\n\n## Development\n\nRun the development version with\n\n```\nnpm run dev\n```\n\n### nodemon\n\nThis task runs the server with [nodemon](https://github.com/remy/nodemon). Nodemon will restart the server when some of the files specified in [its config](nodemon.json) change.\n\n### Webpack\n\nWebpack is used as commonjs module bundler, css builder (using sass-loader) and assets loader (images and svg files).\n\nThe [development config](./webpack/dev.config.js) enables source maps, the [Hot Module Replacement](http://webpack.github.io/docs/hot-module-replacement.html) and [react-hot-loader](http://gaearon.github.io/react-hot-loader/). It loads CSS styles with `\u003cstyle\u003e`, to enable styles live reload). This config is used by the [webpack-dev-server](webpack/server.js), serving the files bundled by Webpack.\n\n\u003e This config uses the [webpack-error-notification](https://github.com/vsolovyov/webpack-error-notification)\n\u003e plugin. To get notified on errors while compiling the code, on Mac you must `brew install terminal-notifier`.\n\nThe [production config](./webpack/prod.config.js) builds the client-side production bundle from `npm run build`.\n\nBoth configs set a `process.env.BROWSER` global variable, useful to require CSS from the components, e.g:\n\n```js\n// MyComponent\nif (process.env.BROWSER) {\n  require('../style/MyComponent.scss');\n}\n```\n\nOn production, files bundled by webpack are hashed. Javascript and CSS file names are saved in a `static/dists/stats.json` which is read by the [Html](src/containers/Html.js) component.\n\n### Babeljs\n\nThis app is written in Javascript-[Babel](https://babeljs.io/). Babel config is in [.babelrc](.babelrc) (it only enables class properties). On Sublime Text, I installed [babel-sublime](https://github.com/babel/babel-sublime) to have full support of the Babel syntax!\n\n### .editorconfig\n\nThe [.editorconfig](.editorconfig) file can be used with your IDE/editor to mantain a consistent coding style. See [editorconfig.org](http://editorconfig.org) for more info. (thanks to @lohek)\n\n### Linting\n\nI use [eslint](http://eslint.org) with [babel-eslint](https://github.com/babel/babel-eslint) and the [react plugin](https://github.com/yannickcr/eslint-plugin-react). I also configured Sublime Text with [SublimeLinter-eslint](https://github.com/roadhump/SublimeLinter-eslint).\n\nI use the rules from my own [eslint-config-gpbl](https://github.com/gpbl/eslint-config-gpbl) shared configs.\n\n```bash\nnpm run lint\n```\n\nI use [SublimeLinter-scss-lint](https://github.com/attenzione/SublimeLinter-scss-lint) for linting the Sass files ([.scss-lint.yml](.scss-lint.yml)) (only with Sublime Text).\n\n### Debugging\n\nThe app uses [debug](https://www.npmjs.com/package/debug) to log debug messages. You can enable/disable the logging from Node by setting the `DEBUG` environment variable before running the server:\n\n```bash\n# enable logging for isomorphic500 and Fluxible\nDEBUG=isomorphic500,Fluxible node index\n\n# disable logging\nDEBUG= node index\n```\n\nFrom the **browser**, you can enable/disable them by sending this command in the JavaScript console:\n\n```js\ndebug.enable('isomorphic500')\ndebug.disable()\n// then, refresh!\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgpbl%2Fisomorphic500","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgpbl%2Fisomorphic500","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgpbl%2Fisomorphic500/lists"}