ReactJS #29: Aplikasi Blog Sederhana
Dengan API dari WordPress
Halo, arsitek konten! 🏛️
Selamat datang di proyek akhir kita! Setelah belajar banyak hal, sekarang saatnya menggabungkan semuanya untuk membuat aplikasi blog yang sesungguhnya. Kita akan menggunakan WordPress sebagai backend (penyedia data) dan React sebagai frontend (tampilan). Ibarat restoran, WordPress adalah dapur yang menyimpan dan mengelola resep, React adalah pelayan yang menyajikan makanan dengan cantik ke meja tamu.
💡 Konsep Headless CMS: WordPress biasanya dipakai untuk membuat website lengkap dengan tampilan. Tapi dengan fitur REST API, WordPress bisa dijadikan "kepala yang terpisah" (headless) – hanya mengelola konten, sedangkan tampilan diatur oleh React. Ini memberi kita kebebasan total dalam mendesain! [citation:2][citation:3]
📋 Apa yang Akan Kita Buat?
- Mengambil data postingan dari WordPress REST API.
- Menampilkan daftar postingan dalam bentuk kartu (blog card).
- Membuat halaman detail untuk setiap postingan (dengan React Router).
- Menampilkan gambar unggulan (featured image) dan penulis.
- Menambahkan custom fields (opsional) dengan Advanced Custom Fields (ACF).
⚙️ Langkah 1: Setup WordPress Lokal (Dapur Kita)
Kita perlu WordPress yang berjalan di komputer lokal. Gunakan XAMPP atau Local by Flywheel untuk menjalankan WordPress. Berikut langkah cepatnya:
- Instal XAMPP, jalankan Apache dan MySQL.
- Download WordPress dari wordpress.org, ekstrak ke folder
htdocs (misal C:\xampp\htdocs\wp-blog).
- Buka phpMyAdmin, buat database baru (misal
wp_react).
- Jalankan installer WordPress dengan membuka
http://localhost/wp-blog di browser. Ikuti petunjuknya. [citation:1]
Setelah WordPress terinstal, masuk ke dashboard dan buat beberapa postingan contoh (minimal 3). Upload juga gambar unggulan untuk setiap postingan.
🔧 Setting Permalink (Penting!)
Di dashboard WordPress, masuk ke Settings > Permalinks, pilih opsi "Post name". Ini membuat URL postingan jadi bersih (misal /hello-world) dan memudahkan routing nanti. [citation:1]
📦 Install Plugin (Opsional Tapi Disarankan)
- Advanced Custom Fields (ACF): Untuk menambahkan field kustom ke postingan (misal subjudul, rating). [citation:3][citation:6]
- JWT Authentication for WP REST API: Jika nanti ingin menambah fitur login/upload dari React. Untuk blog sederhana, tidak wajib. [citation:1]
🔌 Mengecek REST API WordPress
WordPress secara otomatis menyediakan REST API. Coba buka di browser: http://localhost/wp-blog/wp-json/wp/v2/posts. Kamu akan melihat data JSON berisi postingan-postinganmu. [citation:1][citation:7]
Untuk mendapatkan data yang lebih lengkap (termasuk featured image dan author), tambahkan parameter ?_embed: [citation:9]
http://localhost/wp-blog/wp-json/wp/v2/posts?_embed
Data yang di-embed akan menyertakan author, media, dan komentar dalam satu respons.
⚛️ Langkah 2: Setup React Frontend (Toko Kita)
Buka terminal, buat proyek React baru (kita pakai Vite karena lebih cepat): [citation:8]
npm create vite@latest react-wordpress-blog -- --template react
cd react-wordpress-blog
npm install
npm install axios react-router-dom
npm run dev
Atau jika ingin pakai Create React App:
npx create-react-app react-wordpress-blog
cd react-wordpress-blog
npm install axios react-router-dom
npm start
📁 Langkah 3: Struktur Folder
Buat folder dan file seperti ini (sesuai artikel #26):
src/
├── components/
│ ├── BlogCard.jsx
│ └── LoadingSpinner.jsx
├── pages/
│ ├── Home.jsx
│ └── PostDetail.jsx
├── services/
│ └── wordpress.js
├── App.jsx
└── main.jsx
🔧 Langkah 4: Service untuk Memanggil API
Buat file src/services/wordpress.js untuk menampung semua fungsi API: [citation:7]
import axios from 'axios';
// Ganti dengan URL WordPress lokalmu
const API_URL = 'http://localhost/wp-blog/wp-json/wp/v2';
export const getPosts = async (page = 1, perPage = 5) => {
try {
const response = await axios.get(`${API_URL}/posts`, {
params: {
_embed: true,
page,
per_page: perPage
}
});
return {
posts: response.data,
totalPages: response.headers['x-wp-totalpages']
};
} catch (error) {
console.error('Error fetching posts:', error);
throw error;
}
};
export const getPostBySlug = async (slug) => {
try {
const response = await axios.get(`${API_URL}/posts`, {
params: {
slug,
_embed: true
}
});
return response.data[0]; // karena slug unik, array berisi satu item
} catch (error) {
console.error('Error fetching post:', error);
throw error;
}
};
export const getPostById = async (id) => {
try {
const response = await axios.get(`${API_URL}/posts/${id}?_embed`);
return response.data;
} catch (error) {
console.error('Error fetching post:', error);
throw error;
}
};
🃏 Langkah 5: Komponen BlogCard
Buat src/components/BlogCard.jsx untuk menampilkan satu postingan dalam bentuk kartu: [citation:6][citation:9]
import { Link } from 'react-router-dom';
function BlogCard({ post }) {
// Ambil featured image dari _embedded
const featuredImage = post._embedded?.['wp:featuredmedia']?.[0]?.source_url;
const author = post._embedded?.author?.[0]?.name || 'Admin';
const date = new Date(post.date).toLocaleDateString('id-ID', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
return (
<div className="blog-card">
{featuredImage && (
<img
src={featuredImage}
alt={post.title.rendered}
style={{ width: '100%', borderRadius: '30px', marginBottom: '15px' }}
/>
)}
<h3 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
<div className="meta">
<span>✍️ {author}</span> | <span>📅 {date}</span>
</div>
<div className="excerpt" dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
<Link to={`/post/${post.slug}`}>
<button>Baca Selengkapnya</button>
</Link>
</div>
);
}
export default BlogCard;
🏠 Langkah 6: Halaman Utama (Home)
Buat src/pages/Home.jsx yang menampilkan daftar postingan: [citation:1][citation:7]
import { useState, useEffect } from 'react';
import { getPosts } from '../services/wordpress';
import BlogCard from '../components/BlogCard';
import LoadingSpinner from '../components/LoadingSpinner';
function Home() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
useEffect(() => {
const fetchPosts = async () => {
try {
setLoading(true);
const data = await getPosts(page, 5);
setPosts(data.posts);
setTotalPages(data.totalPages);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchPosts();
}, [page]);
if (loading) return <LoadingSpinner />;
if (error) return <p>❌ Error: {error}</p>;
return (
<div>
<h1>📰 Blog Sederhana dari WordPress</h1>
{posts.map(post => (
<BlogCard key={post.id} post={post} />
))}
<div style={{ display: 'flex', gap: '20px', justifyContent: 'center', marginTop: '30px' }}>
<button
onClick={() => setPage(p => Math.max(1, p-1))}
disabled={page === 1}
>⬅️ Sebelumnya</button>
<span>Halaman {page} dari {totalPages}</span>
<button
onClick={() => setPage(p => Math.min(totalPages, p+1))}
disabled={page === totalPages}
>Berikutnya ➡️</button>
</div>
</div>
);
}
export default Home;
📄 Langkah 7: Halaman Detail Postingan
Buat src/pages/PostDetail.jsx yang menampilkan satu postingan berdasarkan slug: [citation:4][citation:7]
import { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import { getPostBySlug } from '../services/wordpress';
import LoadingSpinner from '../components/LoadingSpinner';
function PostDetail() {
const { slug } = useParams();
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchPost = async () => {
try {
setLoading(true);
const data = await getPostBySlug(slug);
if (!data) throw new Error('Postingan tidak ditemukan');
setPost(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchPost();
}, [slug]);
if (loading) return <LoadingSpinner />;
if (error) return <p>❌ Error: {error}</p>;
const featuredImage = post._embedded?.['wp:featuredmedia']?.[0]?.source_url;
const author = post._embedded?.author?.[0]?.name || 'Admin';
const date = new Date(post.date).toLocaleDateString('id-ID', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
return (
<div>
<Link to="/">⬅️ Kembali ke Beranda</Link>
<article style={{ marginTop: '30px' }}>
{featuredImage && (
<img
src={featuredImage}
alt={post.title.rendered}
style={{ width: '100%', maxHeight: '400px', objectFit: 'cover', borderRadius: '40px' }}
/>
)}
<h1 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
<p style={{ color: '#b94e4e' }}>✍️ {author} | 📅 {date}</p>
<div dangerouslySetInnerHTML={{ __html: post.content.rendered }} style={{ fontSize: '1.3em' }} />
</article>
</div>
);
}
export default PostDetail;
⏳ Langkah 8: Komponen Loading Spinner
Buat src/components/LoadingSpinner.jsx sederhana:
function LoadingSpinner() {
return (
<div style={{ textAlign: 'center', padding: '40px' }}>
<div className="spinner"></div>
<p>⏳ Memuat data...</p>
</div>
);
}
export default LoadingSpinner;
🧭 Langkah 9: Routing di App.jsx
Gabungkan semua halaman dengan React Router: [citation:2]
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import PostDetail from './pages/PostDetail';
import './App.css';
function App() {
return (
<BrowserRouter>
<div className="App" style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/post/:slug" element={<PostDetail />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
🎨 Langkah 10: Sedikit CSS Global
Tambahkan gaya dasar di src/App.css atau di index.css. Bisa pakai CSS biasa atau Tailwind sesuai selera. Pastikan komponen terlihat rapi.
🧪 Menambahkan Custom Fields (ACF) [Opsional]
Jika ingin menampilkan field kustom (misal "Subjudul" atau "Rating"), kita perlu menginstall plugin ACF di WordPress, lalu membuat field group untuk postingan. Agar field muncul di REST API, tambahkan kode ini di functions.php tema WordPress (atau buat plugin sederhana): [citation:3][citation:6]
function expose_acf_in_rest_api() {
register_rest_field('post', 'custom_fields', array(
'get_callback' => function ($post) {
return get_fields($post['id']);
},
));
}
add_action('rest_api_init', 'expose_acf_in_rest_api');
Setelah itu, field ACF akan tersedia di respons API dengan key custom_fields. Di React, tinggal akses post.custom_fields.nama_field.
💡 Catatan: Jika menggunakan XAMPP, pastikan tidak ada konflik port. Bisa juga gunakan Local by Flywheel yang lebih ramah pemula. Untuk produksi, WordPress bisa di-hosting di server terpisah dan React di Netlify/Vercel dengan setup CORS. [citation:2]
🧪 Latihan: Tambah Kategori dan Tag
Coba perluas aplikasi dengan menampilkan kategori dan tag. Di WordPress REST API, kita bisa mengambil /wp-json/wp/v2/categories dan /wp-json/wp/v2/tags. Tampilkan sebagai filter di halaman utama.
🚀 Kesimpulan
- Kita berhasil membuat blog sederhana dengan React dan WordPress REST API.
- WordPress bertindak sebagai headless CMS – hanya menyediakan data melalui API.
- React mengambil data dengan
axios, menampilkannya dalam komponen-komponen.
- React Router digunakan untuk navigasi antar halaman.
- Kita bisa menambahkan fitur seperti custom fields (ACF), kategori, dan pencarian.
Proyek ini bisa dikembangkan lebih lanjut: tambahkan komentar, formulir kontak, atau autentikasi pengguna. Selamat, kamu telah menyelesaikan tutorial React dari dasar hingga mahir! 🎉
Di artikel terakhir (opsional), kita akan membahas Proyek Akhir: Membuat Aplikasi Portal Berita dengan React dan API (Gabungkan Semua Ilmu!) sebagai tantangan terakhir. Sampai jumpa!