ReactJS : Server & Client Routing

Pada dasarnya, ada tiga macem Routing dalam aplikasi web: Server-side ,Client-side, & kombinasi keduanya. Dalam tutorial ini saya akan bahas tentang routing di sisi klien & server pake React Router.

Sedikit info buat yang belum tau routing .

Server-side Routing

Dalam Server-side Routing semua rekues dihandel server yang akan kirim HTML sebagai respon ke browser.

Jadi misalnya dari home page: www.tiket.com kita buka www.tiket.com/pesawat, rekues dikirim ke server yg kemudian kirim respon HTML yg berisi halaman /pesawat ke browser.

Jadi ini model konvensional. Rekues -> server -> browser refresh.

Ini routing untuk web page ya. Kalo routing untuk REST API, ya nggak perlu browser refresh.

Client-side Routing

Client-side Routing mulai populer dengan adanya dukungan browser utk History API dari spesifikasi HTML5 & munculnya Single-page Application (SPA). Jadi waktu kita buka /pesawat, nggak ada rekues yg dikirim ke server. Rekues dihandel oleh kode JavaScript yang kemudian menampilkan komponen Flight di browser. Kalo kita pindah ke /train , ya yang ditampilin adalah komponen Train.

Dalam sistem yang pure pake Client-side Routing, biasanya semua rekues yang bukan ke “/” (root URL) oleh server dialihkan (redirect) ke index page. Di file index ( bisa html atau php atau yang lain, pokoknya yang ditampilin sbg index page), ada skrip JS yang mendeteksi perubahan state di History API & terus update DOM di halaman index itu.

Contoh aplikasi Client-side Routing bisa diliat di sini:
https://ab-react-routing-browser-only.herokuapp.com/

Beberapa bacaan tentang History API:

Routing dengan Server-side Rendering (Isomorphic Web App)

Kekurangan dari client-side routing adalah dia butuh JavaScript untuk update elemen DOM. Jadi nggak bagus buat SEO karena yang diliat crawler (Google bot dsb) hanya halaman kosong. Sebaliknya, server-side routing nggak bagus karena harus bolak balik kirim rekues & refresh halaman html.

Alternatif yang lebih baik adalah apa yang disebut SSR (Server-side Rendering).

Dalam kasus SSR, di mana state awal aplikasi / page di-render di server, kita pake kombinasi antara Server-side & Client-side Routing. Jadi pada awalnya, server menghandel rekues & mengirim respon HTML ke browser. Ketika modul JavaScript yang isinya aplikasi kita selesai dimuat oleh browser, dia akan mengambil alih kontrol atas elemen DOM.

Setelah itu semua rekues yang asalnya dari aplikasi akan dihandel oleh JS. Tapi hanya rekues yang pake history API yang langsung dihandel JS, sementara yang dimasukin langsung ke address bar atau dibuat diluar browser tanpa dukungan JavaScript (misalnya oleh Robot/crawler) tetep dihandel oleh server.

Dalam tutorial ini, saya akan bahas pembuatan aplikasi sederhana. Super sederhana. Nggak ada bagus-bagusnya. Tapi memang intinya untuk belajar implementasi React Router.

Demo aplikasi yg akan kita buat bisa diliat di sini:

Atau dicoba sendiri di sini: https://ab-react-routing.herokuapp.com/.

Starter Project

Kita mau mulai dari nol ya. Silakan klon template yg saya buat di Gitlab: react-starter. Terus hapus direktori .git & bikin git repo lokal baru.

$ rm -rf .git
$ git init 

Berikutnya, instal semua dependency NPM:

$ yarn install
#atau
$ npm install

Berikutnya, buka file webpack.config.js, di bawah baris 26 tambahin historyApiFallback: true biar bisa support SPA routing. Jadi begini:

commonConfig.devServer({
  contentBase: OUTPUT.path,
  historyApiFallback: true //baris baru
}),

Lanjut, instal NPM react-router-dom.

$ yarn add react-router-dom -E -S
#atau
$ npm install react-router-dom -E

Terus jalanin webpack-dev-server & buka localhost:8080 di browser.

$ yarn wd-server

Hapus file src/client/components/NameInput.jsx & semua reference di file src/client/App.jsx. Di baris yang tadinya berisi <NameInput/>, kasih teks App.

import React from 'react';
export default class App extends React.Component{

  constructor(props){
    super(props);

  }

  render(){
    return (
      <div> 
        App
      </div>
    );
  }
};

Update file src/client/styles/styles.scss, ganti isinya dengan:

body {
  margin:0;padding:0;
  font-family: Arial, Helvetica, sans-serif;

  #app{
    margin:10px;
    background: white;
  }

  .box{
    padding:10px;
    border:1px solid #D0DAFE;
    border-radius: 4px;
    width:300px;
    height:400px;
    overflow: auto;
    margin:10px;
  }

  .red{
    background-color: #FC2C3D;
  }

  .blue{
    background-color: #5EBBFF;
  }

  .green{
    background-color: #72FF9F;
  }

  .footer{
    background-color: #FDE30A;
    height:200px;
  }

  .active{    
    background-color: #4B77FF;
    color:#fff;
  }
}

Buka file src/client/browser.jsx & ganti isinya dengan:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';

import './styles/styles.scss';

ReactDOM.render( <App />, document.getElementById('app') );

Itu aja untuk persiapan. Silakan commit.

$ git add .
$ git commit -m 'prep'

Komponen

Ada dua macam komponen, yang nanti kita buat. Ada yg global, ditampilin di semua route & ada yg spesifik hanya ditampilin di route tertentu.

Yang global adalah komponen Header dan Footer. Nanti ada komponen “Page” yang khusus untuk masing-masing route.

Header Nav

Buat file src/client/components/Header.jsx.

import React from 'react';
//NavLink support styling
import { NavLink } from 'react-router-dom';

const Header = (props) => (
  <ul>
    <li><NavLink activeClassName="active" exact 
          to="/">Home</NavLink></li>
    <li><NavLink activeClassName="active" 
          to="/dashboard">Dashboard</NavLink></li>
    <li><NavLink activeClassName="active" 
          to="/cart">Cart</NavLink></li>
  </ul>
);

export default Header;

Untuk bikin link, kita bisa pake <NavLink> atau <Link>. Bedanya, <NavLink> bisa dikasih css-class lewat prop activeClassName, <Link> nggak bisa.

Komponen <NavLink> nggak bisa dipake sendirian, dia harus ada di bawah komponen <BrowserRouter>. Jadi kita buka file browser.jsx & update sedikit:

import React from 'react';
import ReactDOM from 'react-dom';

//import BrowserRouter
import { BrowserRouter } from 'react-router-dom';
import App from './App.jsx';

import './styles/styles.scss';

//masukin App ke dalem BrowserRouter
const jsx = (
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

//pake jsx yg baru
ReactDOM.render( jsx, document.getElementById('app') );

Buka file App.jsx& kita pake <Header> di render():

import React from 'react';
//import Header dari Header.jsx (jangan lupa ekstensinya)
import Header from './components/Header.jsx';

export default class App extends React.Component{

  constructor(props){
    super(props);
  }

  render(){
    return (
      <div> 
        <Header />
      </div>
    );
  }
};

Properti activeClassName dipake untuk pasang class CSS sesuai route/link yang sedang aktif. Jadi kalo route yg sedang aktif adalah /dashboard, link ke Dashboard punya kelas active.

NOTE: Kelas .active ini dibikin di styles.scss

Properti exact di Home artinya link ini hanya aktif kalo rekuesnya ke root. Jadi hanya localhost:8080 atau localhost:8080/ . Kalo nggak pake exact, dia juga aktif kalo rekuesnya localhost:8080/dashboard karena ada karakter / contohnya begini:

Page

Sekarang kita bikin komponen-komponen “Page” untuk ditampilin di masing-masing route.

Pertama, komponen <Home>. Bikin src/client/components/pages/Home.jsx yg isinya begini:

import React from 'react';
const Home = ( props ) => (
  <div className="box blue"> Home </div>
);

export default Home;

Lanjutin dengan komponen, <Dashboard> & <Cart>. Isinya mirip, hanya ganti warna & teks aja.

// FILE: src/client/components/pages/Dashboard.jsx
import React from 'react';
const Dashboard = ( props ) => (
  <div className="box red"> Dashboard </div>
);

export default Dashboard;

// FILE: src/client/components/pages/Cart.jsx
import React from 'react';
const Cart = ( props ) => (
  <div className="box green"> Cart </div>
);

export default Cart;

Route

Untuk nampilin komponen-komponen “Page”, kita perlu implementasi routing-nya. Bikin file src/client/components/AppRouter.jsx. Di file ini kita bikin implementasi routing:

import React from 'react';
import { Route, Switch } from 'react-router-dom';
import Home from './pages/Home.jsx';
import Dashboard from './pages/Dashboard.jsx';
import Cart from './pages/Cart.jsx';

const AppRouter = (props) => (
  <Switch>
    <Route path="/" component={Home} exact />
    <Route path="/dashboard" component={Dashboard} />
    <Route path="/cart" component={Cart} />
  </Switch>
);

export default AppRouter; 

Jadi dalam setiap <Route> kita tentuin komponen apa yang mau ditampilin:
<Home> untuk route /
<Dashboard> untuk route /dashboard
<Cart> untuk route /cart

React router pake metode pattern-matching jadi karena kita hanya ingin nampilin satu komponen per route, kita pake <Switch>.

Untuk lebih detilnya, tentang komponen <Switch> bisa dibaca sendiri di dokumentasinya.

Sekarang kita tinggal pake komponen ini di App.jsx:

import React from 'react';
import Header from './components/Header.jsx';
//import AppRouter
import AppRouter from './components/AppRouter.jsx';
export default class App extends React.Component{
  render(){
    return (
      <div> 
        <Header />
        <AppRouter />
      </div>
    );
  }
};

Hasilnya kurang lebih begini:

Footer

Ini komponen paling sederhana. Bikin file src/client/components/Footer.jsx, isinya begini aja:

import React from 'react';
const Footer = ( props ) => (
  <div className="box footer"> Static Footer </div>
);
export default Footer; 

Terus tambahin ke App.jsx di render():

render(){
  return (
    <div> 
      <Header />
      <AppRouter />
      <Footer />
    </div>
  );
}

Jangan lupa import Footer from './components/Footer.jsx';

That’s it. Itu aja untuk client-side routing.

Server-side Routing & Rendering

Kita lanjut ke Server. Nyalain dulu server nya: yarn server:watch.

Buka file src/server/index.jsx & ganti isinya dengan ini:


import express from "express"; import react from "react"; import { renderToString } from "react-dom/server"; //import StaticRouter import { StaticRouter } from 'react-router-dom'; import App from "../client/App.jsx"; import fs from "fs"; import path from "path"; //HEROKU process.env const port = process.env.PORT || 3000; const publicPath = path.resolve("dist","public"); const index = fs.readFileSync(publicPath+"/index.html","utf-8"); const app = express(); app.use(express.static(publicPath)); app.get("/", (req,res) =>{ //bukan lagi render <App /> tapi <App /> dibungkus //router kayak di browser bedanya di server kita //pake StaticRouter bukan BrowserRouter const content = renderToString( <StaticRouter> <App /> </StaticRouter> ); const finalHtml = index.replace("<!--APP-->",content); res.send(finalHtml); }); app.listen(port, () => { console.log("server is up"); });

Jalanin perintah : yarn build:all:prod & terus buka localhost:3000.

Navigasi lewat menu ( klik link home / dashboard / cart ) nggak ada masalah, tapi kalo kita input langsung routenya di address bar kita dapet 404.

Ini karena server hanya handel rekues ke root ( / ), jadi kita ganti baris app.get() jadi begini:

app.get('*', (req, res) => { 
    //dst sama seperti di atas
})

Coba lagi. Harusnya sekarang udah bener 🙂

Jadi hasilnya kayak di video ini :

Sourcecode final bisa di-checkout di sini:
https://gitlab.com/masputih/react-routing

Sekian tutorialnya. Nanti dilanjutin lagi dengan topik (rencananya) Protected & Public Routes. Insya Allah.

Also in this category ...