PHP with ReactJs - End to end setup

Setting up ReactJs with PHP 7 using Laravel framework

tl;dr When I had published an article about setting up development workflow for ReactJs, it was just a basic introduction to production ready dev. In this article I’ll be discussing about setting up development enviournment with PHP, where I’ll be covering some basics which will be helpful in case you want to setup with any other language than PHP with ReactJs. As framework in PHP I’ve selected Laravel 5.1 to explain things in this article, but it’ll be applicable to all the frameworks.

Before we start, I would like to mention that all the steps mentioned here are tested on Ubuntu 14.04 server, but if you are using Mac OS or Windows I will suggest you to take a look at Vagrant if it helps, also take a look at VagrantFile in case you choose to install vagrant.

Before we start, we need LAMP stack running so that we can test our changes, here is an article by Justin Ellingwood which I would suggest to follow up for installing and setting up LAMP stack. Or if you would like to install and setup PHP 7, fire below commands in your terminal and follow the instructions-

sudo LC_ALL=en_US.UTF-8 add-apt-repository ppa:ondrej/php
sudo apt-get update

sudo apt-get install -y language-pack-en-base mysql-server-5.6 apache2 php7.0 php7.0-mysql php7.0-cli php7.0-mcrypt php7.0-curl php7.0-mbstring php7.0-fpm php7.0-xml php7.0-xmlrpc curl git zip unzip libapache2-mod-php7.0 nodejs npm

curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer

sudo mysql_secure_installation
sudo ln -s `which nodejs` /usr/bin/node

Now we can create Laravel project by running following command-

cd ~/
composer create-project --prefer-dist laravel/laravel <MyProject>

Where you can replace the projcet name MyProject with desired name; after you run this command it will fetch all the dependency under the MyProject directory in our case. Once it is done, you can configure Laravel directory to be served by Apache by pointing to ~/MyProject/public directory. My sample apache configuraion looks like below snippet-

<VirtualHost *:80>
	ServerAdmin me@example.com
	DocumentRoot /path/to/public/dir

	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

	<Directory /path/to/public/dir>
		Options Indexes FollowSymLinks MultiViews
		AllowOverride All
		Order allow,deny
		allow from all
	</Directory>
</VirtualHost>

And below is the content of ~/MyProject/public/.htaccess file, which allows us to have clean URL-

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    RewriteBase /
    RewriteRule ^index\.php$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.php [L]
</IfModule>

In case you want to install Sass, here is the article which can be followed. Now let’s take a look at directory structure of the project we created in ~/MyProject/

MyProject/
	app/
	bootstrap/
	config/
	database/
		migrations/
	public/
		.htaccess
		app.css
		index.php
		js/
	resources/
		sass/
		componenets/
		lang/
		views/
	storage/
	tests/
	vendor/
	.env
	...

So here is breif description about the directories which we’ll discuss in the article-

  1. app- The application code resides under this direcotory.
  2. public- This directory keeps all the public assets, and if you will notice this is the same directory which we have added in apache configuration.
  3. resources- This directory keeps all the raw Sass and React components.

Now create a file as ~/MyProject/resources/components/package.json with following content-

{
	"name": "components",
	"version": "0.0.0",
	"description": "",
	"main": "index.js",
	"scripts": {
		"test": "echo \"Error: no test specified\" && exit 1"
	},
	"author": "Ritesh Shrivastav",
	"license": "BSD-2-Clause",
	"devDependencies": {
		"babel-loader": "~6.2.4",
		"babel-plugin-add-module-exports": "~0.1.2",
		"babel-plugin-react-html-attrs": "~2.0.0",
		"babel-plugin-transform-class-properties": "~6.6.0",
		"babel-plugin-transform-decorators-legacy": "~1.3.4",
		"babel-preset-es2015": "~6.6.0",
		"babel-preset-react": "~6.5.0",
		"babel-preset-stage-0": "~6.5.0"
	},
	"dependencies": {
		"history": "~2.0.1",
		"react": "~0.14.7",
		"react-dom": "~0.14.7",
		"react-router": "~2.0.0",
		"webpack": "~1.12.14",
		"axios": "~0.9.1",
		"react-helmet": "~3.0.1"
	}
}

And ~/MyProject/resources/components/webpack.config.js with following content-

var fs = require('fs');
var path = require('path');
var webpack = require('webpack');

module.exports = {

  devtool: 'source-map',

  entry: './client.js',

  output: {
    path            : __dirname + '../../../public/js/build',
    filename        : '[name].js',
    chunkFilename   : '[name]-[chunkhash].js',
    publicPath      : '/js/build/'
  },

  progress: true,

  module: {
    loaders: [ {
      test    : /\.js?$/,
      loader  : 'babel-loader',
      exclude : /node_modules/,
      query   : {
        presets : ['react', 'es2015', 'stage-0'],
        plugins : ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy']
      }
    } ]
  },

  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compress : {
        warnings : false
      }
    }),
    new webpack.optimize.CommonsChunkPlugin('shared.js'),
    new webpack.optimize.MinChunkSizePlugin({minChunkSize: 1000}),
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify('production'),
      }
    })
  ]
}

If you will notice the content of webpack configuration, I have specified client.js to be entry point. Below is the content of client.js file, which should be kept under the components/ directory-

import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import App from './app/App';
import NotFound from './app/errors/NotFound';
import React from 'react';
import ReactDOM from 'react-dom';

const app = document.getElementById('app');

ReactDOM.render(
  <Router history={browserHistory}>
    <Route path="/" component={App}>
      <IndexRoute
        getComponent={(location, callback) => {
          require.ensure([], function (require) {
            callback(null, require('./app/native/NativeSearch').default);
          });
        }}
      />
      <Route
        path="search"
        getComponent={(location, callback) => {
          require.ensure([], function (require) {
            callback(null, require('./app/native/SearchResult').default);
          });
        }}
      />
      <Route name="404" path="*" component={NotFound} />
    </Route>
  </Router>,
app);

Few basic notes about client.js

  • On the top of client.js, if you will notice I have imported modules in which App, NotFound are the react components. If you are saving react components with .jsx extension then this will not work instead you need to import those modules by writing complete extension of the file. But if you want to avoid that, save file with .js extension.
  • Webpack provides require.ensure method, which can be used to split the code into chunks.
  • For handling 404 routes we have configured NotFound component, so basically using path="*" we can handle all the requests for which it didn’t matched with the above paths.

Since we have added package.json and webpack configuration, now we can install the dependency by running following command-

cd ~/MyProject/resources/components/
npm install
# we need it
sudo npm install webpack -g

Once npm installs webpack globaly, we can use webpack command to bundle our components, and on successfull build it will generate two files shared.js and main.js. In laravel my layout file resources/views/layouts/main.blade.php contains following code-

<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <link rel="stylesheet" href="/app.css">
  <link rel="icon" href="icon.png?v=1.0.0" type="image/png">
  @yield('head')
</head>
<body>
  <div id="app">
    @yield('content')
  </div>
  <script type="text/javascript" src="/js/build/shared.js"></script>
  <script type="text/javascript" src="/js/build/main.js"></script>
</body>
</html>

Which can be used by any further blade files. Below are few basic problems what each React developers face initially-

SEO friendly React apps

Having SEO friendly app is the biggest issue when we use any javascript frameworks because all search engines doesn’t execute javascript after crawling(now a days few web-crawler does it, such as google).

There are two types of routing in React: push-state, and hash-based. Each of them have their strengths and weaknesses. Here is a good writeup by James about routing.

In case if you are using push-state routing, you’ll need to handle requests from server-side for all the URLs as well. And if your application is hidden behind login screen this’ll not be the perfect option to choose. But in such situation when your application content is live to public, then it makes sense to have pretty URLs.

Speed up loading of App

When we talk about loading a single page application without any delay, generally this issue is caused because we load our whole application at once. But if we divide our code in such a manner, such that if X component is required than only it should load X’s definition. This problem can be addressed by dividing build code into chunks, and it’s done effectively by webpack and react-router; which we have used.

Published
Like it? Tweet.

Follow me on Twitter

I tweet about tech more than I write about it here 😀

Ritesh Shrivastav