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 ...
- » Flexbox
- » Mengenal Hook di ReactJS
- » Rust 101: Hello Rust
- » JS : Pemrograman Asinkron
- » End-to-end Testing dengan The Intern