Skip to main content

Digital Marketing Meningkatkan Kapasitas dan Daya saing UMKM naik kelas

Deskripsi Pelatihan Pengabdian Masyarakat: Pengembangan Website UMKM untuk Digitalisasi Bisnis Pelatihan Pengabdian kepada Masyarakat (PkM) ini bertujuan untuk memberdayakan pelaku UMKM melalui peningkatan kemampuan dalam memanfaatkan website sebagai media pemasaran digital. Kegiatan ini dirancang untuk membantu UMKM beradaptasi dengan transformasi digital sehingga mampu memperluas jangkauan pasar, meningkatkan penjualan, dan memperkuat branding usaha. Dalam pelatihan ini, peserta UMKM akan mendapatkan pendampingan langsung mengenai cara membuat, mengelola, dan mengoptimalkan website sebagai sarana informasi dan promosi produk. Pendampingan juga mencakup integrasi website dengan strategi digital marketing seperti SEO dasar, Google Business Profile, media sosial, katalog online, serta teknik meningkatkan konversi penjualan. 🎯 Tujuan Pelatihan Meningkatkan literasi digital pelaku UMKM. Membekali peserta dengan kemampuan membuat dan mengelola website secara mandiri. Mendor...

Buat Teknologi untuk Project Marketplace UMKM

Buat Latihan project Laravel marketplace minimal, siap dijalankan di XAMPP, dan saya sertakan semua file penting yang kamu butuh untuk menjalankan MVP (auth, produk, kategori, cart session, checkout sederhana, admin CRUD, seeder, storage link, dan instruksi).



Di bawah ini saya paste file-by-file yang perlu kamu buat/masukkan ke project (salin-tempel). Ikuti urutan supaya lebih rapi.

Asumsi: kamu pakai Laravel 10+, PHP ≥ 8.1, MySQL via XAMPP, project diletakkan di C:\xampp\htdocs\marketplace (Windows) atau /opt/lampp/htdocs/marketplace (Linux).
Jika mau, setelah ini saya juga bisa rapikan jadi checklist file yang bisa kamu copy-paste satu per satu.


1) FILE: .env.example

Salin ke .env lalu sesuaikan.

APP_NAME=Marketplace APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=http://localhost:8000 LOG_CHANNEL=stack DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=marketplace_db DB_USERNAME=root DB_PASSWORD= FILESYSTEM_DRIVER=public

2) README singkat (README.md)

# Marketplace (Laravel) - Minimal MVP Requirements: - PHP 8.1+, Composer, Node.js (optional for assets) - XAMPP (Apache + MySQL) Setup: 1. letakkan folder project di htdocs 2. copy .env.example -> .env, set DB 3. run: composer install 4. run: php artisan key:generate 5. run: php artisan migrate --seed 6. run: php artisan storage:link 7. run: php artisan serve --port=8000 (atau akses via virtualhost XAMPP)

3) MIGRATIONS (database/migrations)

a) 2025_01_01_000001_add_is_admin_to_users_table.php

<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(){ Schema::table('users', function(Blueprint $table){ $table->boolean('is_admin')->default(false)->after('password'); }); } public function down(){ Schema::table('users', function(Blueprint $table){ $table->dropColumn('is_admin'); }); } };

b) 2025_01_01_000002_create_categories_table.php

<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(){ Schema::create('categories', function(Blueprint $table){ $table->id(); $table->string('name'); $table->string('slug')->unique(); $table->timestamps(); }); } public function down(){ Schema::dropIfExists('categories'); } };

c) 2025_01_01_000003_create_products_table.php

<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(){ Schema::create('products', function(Blueprint $table){ $table->id(); $table->string('name'); $table->string('slug')->unique(); $table->text('description')->nullable(); $table->decimal('price',12,2); $table->integer('stock')->default(0); $table->string('image')->nullable(); $table->foreignId('category_id')->constrained()->onDelete('cascade'); $table->timestamps(); }); } public function down(){ Schema::dropIfExists('products'); } };

d) 2025_01_01_000004_create_orders_and_order_items.php

<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(){ Schema::create('orders', function(Blueprint $table){ $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->decimal('total',12,2); $table->string('status')->default('pending'); $table->string('payment_method')->nullable(); $table->timestamps(); }); Schema::create('order_items', function(Blueprint $table){ $table->id(); $table->foreignId('order_id')->constrained()->onDelete('cascade'); $table->foreignId('product_id')->constrained()->onDelete('cascade'); $table->integer('qty'); $table->decimal('price',12,2); $table->timestamps(); }); } public function down(){ Schema::dropIfExists('order_items'); Schema::dropIfExists('orders'); } };

4) MODELS (app/Models)

a) Category.php

<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Category extends Model { use HasFactory; protected $fillable = ['name','slug']; public function products(){ return $this->hasMany(Product::class); } }

b) Product.php

<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Product extends Model { use HasFactory; protected $fillable = ['name','slug','description','price','stock','image','category_id']; public function category(){ return $this->belongsTo(Category::class); } }

c) Order.php

<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Order extends Model { use HasFactory; protected $fillable = ['user_id','total','status','payment_method']; public function items(){ return $this->hasMany(OrderItem::class); } public function user(){ return $this->belongsTo(User::class); } }

d) OrderItem.php

<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class OrderItem extends Model { use HasFactory; protected $fillable = ['order_id','product_id','qty','price']; public function product(){ return $this->belongsTo(Product::class); } }

5) CONTROLLERS (app/Http/Controllers)

a) HomeController.php

<?php namespace App\Http\Controllers; use App\Models\Product; use App\Models\Category; class HomeController extends Controller { public function index(){ $categories = Category::withCount('products')->get(); $products = Product::with('category')->orderBy('created_at','desc')->paginate(12); return view('home', compact('products','categories')); } }

b) ProductController.php

<?php namespace App\Http\Controllers; use App\Models\Product; use Illuminate\Http\Request; class ProductController extends Controller { public function index(Request $r){ $q = $r->query('q'); $products = Product::with('category') ->when($q, fn($db)=>$db->where('name','like','%'.$q.'%')) ->paginate(12); return view('products.index', compact('products')); } public function show($slug){ $product = Product::where('slug',$slug)->with('category')->firstOrFail(); return view('products.show', compact('product')); } }

c) CartController.php

<?php namespace App\Http\Controllers; use App\Models\Product; use Illuminate\Http\Request; class CartController extends Controller { public function index(){ $cart = session('cart', []); return view('cart.index', compact('cart')); } public function add(Request $r, $id){ $product = Product::findOrFail($id); $cart = session('cart', []); if(isset($cart[$id])) $cart[$id]['qty']++; else $cart[$id] = ['id'=>$id,'name'=>$product->name,'price'=>$product->price,'qty'=>1,'image'=>$product->image]; session(['cart'=>$cart]); return redirect()->back()->with('success','Produk ditambahkan ke keranjang'); } public function update(Request $r, $id){ $cart = session('cart', []); if(isset($cart[$id])){ $cart[$id]['qty'] = max(1,(int)$r->qty); session(['cart'=>$cart]); } return back(); } public function remove($id){ $cart = session('cart', []); if(isset($cart[$id])){ unset($cart[$id]); session(['cart'=>$cart]); } return back(); } }

d) CheckoutController.php

<?php namespace App\Http\Controllers; use App\Models\Order; use App\Models\OrderItem; use App\Models\Product; use Illuminate\Http\Request; class CheckoutController extends Controller { public function index(){ $cart = session('cart', []); if(empty($cart)) return redirect()->route('cart.index')->with('error','Keranjang kosong'); return view('checkout.index', compact('cart')); } public function process(Request $r){ $user = auth()->user(); if(!$user) return redirect()->route('login'); $cart = session('cart', []); if(empty($cart)) return redirect()->route('cart.index')->with('error','Keranjang kosong'); $total = collect($cart)->sum(fn($i)=> $i['price'] * $i['qty']); $order = Order::create(['user_id'=>$user->id,'total'=>$total,'status'=>'pending','payment_method'=>$r->payment_method ?? 'manual']); foreach($cart as $item){ OrderItem::create(['order_id'=>$order->id,'product_id'=>$item['id'],'qty'=>$item['qty'],'price'=>$item['price']]); $p = Product::find($item['id']); if($p) $p->decrement('stock', $item['qty']); } session()->forget('cart'); return redirect()->route('home')->with('success','Order berhasil dibuat. ID: '.$order->id); } }

e) Admin/ProductAdminController.php

<?php namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Models\Product; use App\Models\Category; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; class ProductAdminController extends Controller { public function __construct(){ $this->middleware(['auth','is_admin']); } public function index(){ $products = Product::with('category')->paginate(20); return view('admin.products.index', compact('products')); } public function create(){ $categories = Category::all(); return view('admin.products.create', compact('categories')); } public function store(Request $r){ $r->validate(['name'=>'required','slug'=>'required|unique:products','price'=>'required|numeric','stock'=>'required|integer','category_id'=>'required']); $data = $r->only(['name','slug','description','price','stock','category_id']); if($r->hasFile('image')) $data['image'] = $r->file('image')->store('products','public'); Product::create($data); return redirect()->route('admin.products.index')->with('success','Produk ditambahkan'); } public function edit($id){ $product = Product::findOrFail($id); $categories = Category::all(); return view('admin.products.edit', compact('product','categories')); } public function update(Request $r,$id){ $product = Product::findOrFail($id); $r->validate(['name'=>'required','slug'=>'required|unique:products,slug,'.$product->id,'price'=>'required|numeric','stock'=>'required|integer','category_id'=>'required']); $data = $r->only(['name','slug','description','price','stock','category_id']); if($r->hasFile('image')){ if($product->image) Storage::disk('public')->delete($product->image); $data['image'] = $r->file('image')->store('products','public'); } $product->update($data); return redirect()->route('admin.products.index')->with('success','Produk diupdate'); } public function destroy($id){ $product = Product::findOrFail($id); if($product->image) Storage::disk('public')->delete($product->image); $product->delete(); return back()->with('success','Produk dihapus'); } }

6) MIDDLEWARE IsAdmin (app/Http/Middleware/IsAdmin.php)

<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; class IsAdmin { public function handle(Request $request, Closure $next){ if(auth()->check() && auth()->user()->is_admin) return $next($request); abort(403,'Unauthorized'); } }

Daftarkan di app/Http/Kernel.php pada $routeMiddleware:

'is_admin' => \App\Http\Middleware\IsAdmin::class,

7) ROUTES (routes/web.php)

<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\HomeController; use App\Http\Controllers\ProductController; use App\Http\Controllers\CartController; use App\Http\Controllers\CheckoutController; use App\Http\Controllers\Admin\ProductAdminController; Route::get('/', [HomeController::class,'index'])->name('home'); Route::get('/products', [ProductController::class,'index'])->name('products.index'); Route::get('/product/{slug}', [ProductController::class,'show'])->name('product.show'); Route::get('/cart', [CartController::class,'index'])->name('cart.index'); Route::post('/cart/add/{id}', [CartController::class,'add'])->name('cart.add'); Route::post('/cart/update/{id}', [CartController::class,'update'])->name('cart.update'); Route::post('/cart/remove/{id}', [CartController::class,'remove'])->name('cart.remove'); Route::middleware(['auth'])->group(function(){ Route::get('/checkout', [CheckoutController::class,'index'])->name('checkout.index'); Route::post('/checkout', [CheckoutController::class,'process'])->name('checkout.process'); }); Route::prefix('admin')->middleware(['auth','is_admin'])->group(function(){ Route::resource('products', ProductAdminController::class, ['as'=>'admin']); }); require __DIR__.'/auth.php';

8) VIEWS — penting (simpan di resources/views)

Sebagian view utama; sesuaikan CSS/JS jika perlu.

a) layouts/app.blade.php

<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>@yield('title','Marketplace')</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container"> <a class="navbar-brand" href="{{ route('home') }}">Marketplace</a> <div class="collapse navbar-collapse"> <ul class="navbar-nav ms-auto"> <li class="nav-item"><a class="nav-link" href="{{ route('products.index') }}">Produk</a></li> <li class="nav-item"><a class="nav-link" href="{{ route('cart.index') }}">Keranjang ({{ count(session('cart',[])) }})</a></li> @auth @if(auth()->user()->is_admin) <li class="nav-item"><a class="nav-link" href="{{ url('admin/products') }}">Admin</a></li> @endif <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">{{ auth()->user()->name }}</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="#" onclick="event.preventDefault();document.getElementById('logout-form').submit();">Logout</a></li> </ul> </li> <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display:none">@csrf</form> @else <li class="nav-item"><a class="nav-link" href="{{ route('login') }}">Login</a></li> <li class="nav-item"><a class="nav-link" href="{{ route('register') }}">Register</a></li> @endauth </ul> </div> </div> </nav> <div class="container mt-4"> @if(session('success'))<div class="alert alert-success">{{ session('success') }}</div>@endif @if(session('error'))<div class="alert alert-danger">{{ session('error') }}</div>@endif @yield('content') </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> </html>

b) home.blade.php

@extends('layouts.app') @section('title','Home') @section('content') <div class="row"> <div class="col-md-3"> <h5>Kategori</h5> <ul class="list-group"> @foreach($categories as $c) <li class="list-group-item d-flex justify-content-between align-items-center">{{ $c->name }} <span class="badge bg-primary rounded-pill">{{ $c->products_count }}</span></li> @endforeach </ul> </div> <div class="col-md-9"> <div class="row"> @foreach($products as $p) <div class="col-md-4 mb-3"> <div class="card"> <img src="{{ $p->image ? asset('storage/'.$p->image) : 'https://via.placeholder.com/400x300' }}" class="card-img-top" style="height:200px;object-fit:cover"/> <div class="card-body"> <h5 class="card-title">{{ $p->name }}</h5> <p>Rp {{ number_format($p->price,0,',','.') }}</p> <a href="{{ route('product.show',$p->slug) }}" class="btn btn-sm btn-primary">Detail</a> <form action="{{ route('cart.add',$p->id) }}" method="POST" style="display:inline">@csrf<button class="btn btn-success btn-sm">Tambah</button></form> </div> </div> </div> @endforeach </div> {{ $products->links() }} </div> </div> @endsection

c) products/index.blade.php

(See previous home product listing — you can reuse.)

d) products/show.blade.php

@extends('layouts.app') @section('content') <div class="row"> <div class="col-md-6"><img src="{{ $product->image ? asset('storage/'.$product->image) : 'https://via.placeholder.com/600x400' }}" class="img-fluid"/></div> <div class="col-md-6"> <h3>{{ $product->name }}</h3> <p>Rp {{ number_format($product->price,0,',','.') }}</p> <p>Stok: {{ $product->stock }}</p> <p>{{ $product->description }}</p> <form action="{{ route('cart.add',$product->id) }}" method="POST">@csrf<button class="btn btn-success">Tambah ke Keranjang</button></form> </div> </div> @endsection

e) cart/index.blade.php

@extends('layouts.app') @section('content') <h4>Keranjang</h4> @if(empty($cart))<p>Keranjang kosong.</p> @else <table class="table"><thead><tr><th>Produk</th><th>Harga</th><th>Qty</th><th>Subtotal</th><th></th></tr></thead><tbody> @php $total=0; @endphp @foreach($cart as $c) @php $subtotal = $c['price'] * $c['qty']; $total += $subtotal; @endphp <tr> <td>{{ $c['name'] }}</td> <td>Rp {{ number_format($c['price'],0,',','.') }}</td> <td> <form action="{{ route('cart.update',$c['id']) }}" method="POST" class="d-flex">@csrf <input type="number" name="qty" value="{{ $c['qty'] }}" min="1" class="form-control form-control-sm me-2" style="width:80px"> <button class="btn btn-sm btn-primary">Update</button> </form> </td> <td>Rp {{ number_format($subtotal,0,',','.') }}</td> <td><form action="{{ route('cart.remove',$c['id']) }}" method="POST">@csrf<button class="btn btn-sm btn-danger">Hapus</button></form></td> </tr> @endforeach </tbody></table> <div class="d-flex justify-content-between"> <h5>Total: Rp {{ number_format($total,0,',','.') }}</h5> <a href="{{ route('checkout.index') }}" class="btn btn-success">Checkout</a> </div> @endif @endsection

f) checkout/index.blade.php

@extends('layouts.app') @section('content') <h4>Checkout</h4> @if(empty($cart)) <p>Keranjang kosong</p> @else <form action="{{ route('checkout.process') }}" method="POST">@csrf <div class="mb-3"> <label>Pilih Metode Pembayaran</label> <select name="payment_method" class="form-control"> <option value="bank_transfer">Transfer Bank</option> <option value="cod">Bayar di Tempat (COD)</option> </select> </div> <button class="btn btn-primary">Proses Pesanan</button> </form> @endif @endsection

g) Admin views: resources/views/admin/products/*

(Admin index/create/edit — saya sudah sediakan contoh sebelumnya in the earlier message; paste them into respective files.)


9) SEEDER (database/seeders/SampleSeeder.php)

<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use Illuminate\Support\Str; use App\Models\Category; use App\Models\Product; use App\Models\User; class SampleSeeder extends Seeder { public function run(){ // Admin user (password: password123) User::create([ 'name'=>'Admin', 'email'=>'admin@example.com', 'password'=>bcrypt('password123'), 'is_admin'=>true ]); User::create([ 'name'=>'User', 'email'=>'user@example.com', 'password'=>bcrypt('password123'), 'is_admin'=>false ]); $c1 = Category::create(['name'=>'Elektronik','slug'=>Str::slug('Elektronik')]); $c2 = Category::create(['name'=>'Fashion','slug'=>Str::slug('Fashion')]); Product::create(['name'=>'Headphone ABC','slug'=>'headphone-abc','description'=>'Headphone berkualitas','price'=>250000,'stock'=>10,'category_id'=>$c1->id]); Product::create(['name'=>'Kaos Polos','slug'=>'kaos-polos','description'=>'Kaos cotton 100%','price'=>50000,'stock'=>50,'category_id'=>$c2->id]); } }

Update DatabaseSeeder.php agar memanggil SampleSeeder.


10) Perintah penting setelah menaruh file

Jalankan di terminal project:

composer install cp .env.example .env php artisan key:generate php artisan migrate --seed php artisan storage:link php artisan serve --port=8000

Atau jika di XAMPP, akses http://localhost/marketplace/public (jika meletakkan di htdocs tanpa virtualhost).

Akun admin: admin@example.com / password123


11) Opsi fitur tambahan (opsional, nanti bila ingin dikembangkan)

  • Integrasi payment gateway (Midtrans / Xendit / Stripe).

  • Upload product gallery (multiple images).

  • Seller accounts / multi-vendor (setiap UMKM punya dashboard sendiri).

  • Rating & review, pencarian lanjutan, filter berdasarkan kategori & harga.

  • Tema (themes folder + settings table untuk kustomisasi warna/logo via admin).

Comments

Popular posts from this blog

Penjelasan lengkap tentang Tugas dan Fungsi Keahlian Pranata Komputer

 P enjelasan lengkap tentang Tugas dan Fungsi Keahlian Pranata Komputer sesuai dengan karakteristik jabatan fungsional di instansi pemerintah Indonesia (berdasarkan prinsip umum JF Pranata Komputer, peraturan BKN, dan kebutuhan organisasi modern): 📌 Penjelasan Tugas dan Fungsi Keahlian Pranata Komputer Pranata Komputer adalah jabatan fungsional yang bertugas mengembangkan, mengelola, dan memelihara sistem teknologi informasi dalam instansi pemerintah. Jabatan ini memiliki tingkatan keterampilan (pemula–mahir) dan keahlian (ahli pertama–utama). Berikut uraian lengkap tugas dan fungsinya: 1. Tugas Utama Pranata Komputer A. Pengembangan Sistem Informasi Meliputi: Menganalisis kebutuhan pengguna (user requirement analysis). Mendesain arsitektur sistem: basis data, UI/UX, alur proses. Mengembangkan aplikasi berbasis web, mobile, atau desktop. Membuat dokumentasi teknis pengembangan sistem. Melakukan uji coba sistem (testing) dan debugging. Mengimplementasika...

Mengenal Dunia Konten Digital Kreator di Jogjakarta: Kreativitas Tanpa Batas di Kota Budaya

  “Mengenal Dunia Konten Digital Kreator di Jogjakarta: Kreativitas Tanpa Batas di Kota Budaya” 🧭 Deskripsi Blog / Meta Description (untuk SEO): Jogjakarta kini bukan hanya kota pelajar dan budaya, tapi juga pusat berkembangnya para digital kreator . Artikel ini membahas bagaimana para kreator muda di Jogja membangun karier di dunia digital melalui konten kreatif, edukatif, dan bernilai ekonomi tinggi. 📖 Isi Artikel (Contoh Ringkasan): 1. Jogjakarta: Kota Pelajar, Kini Jadi Kota Kreator Digital Dengan atmosfer yang santai dan biaya hidup yang terjangkau, Jogja menjadi tempat ideal bagi anak muda untuk mengembangkan ide dan bereksperimen di dunia digital. Banyak content house , studio kreatif, dan komunitas digital bermunculan di kota ini. 2. Komunitas Kreatif dan Kolaborasi Lokal Komunitas seperti Jogja Digital Valley, Jogja Creators, dan berbagai coworking space menyediakan ruang belajar dan kolaborasi bagi para digital creator . Mereka tidak hanya berbagi ilmu tentang p...

Cara Membuat Konten Digital Cepat Terindex Google

 Cara membuat Konten Digital   Kalau tujuanmu bikin konten digital (artikel, blog, website, atau postingan) agar cepat terindeks Google , ada beberapa strategi SEO dasar yang bisa diterapkan supaya kontenmu cepat muncul di pencarian . 📌 Strategi Bikin Konten Digital Cepat Terindeks Google 1. Riset Kata Kunci (Keyword Research) Gunakan Google Keyword Planner / Ubersuggest / Keywordtool.io . Cari kata kunci long tail (contoh: “peluang bisnis 2025 untuk pemula modal kecil” lebih cepat naik dibanding “bisnis 2025” ). Fokus ke volume kecil – kompetisi rendah biar cepat terindeks. 2. Struktur Konten SEO-Friendly Gunakan format berikut di artikel atau postingan: Judul (H1): Mengandung kata kunci utama. Sub Judul (H2/H3): Pecah poin-poin konten (Google suka yang terstruktur). Paragraf singkat: 2–3 kalimat, mudah dibaca. Bullet point / list: Membuat konten lebih jelas. Internal link: Hubungkan dengan artikel lain (kalau sudah punya beberapa). ...