Cara membuat blog dengan Nuxt

Peralatan yang digunakan:

Memulai proyek

Mari buat proyek Nuxt baru menggunakan baris perintah berikut.

npm create nuxt nuxt-blog

Pilih jawaban sesuai dengan panduan berikut. Gunakan tombol panah untuk memilih dan tekan enter untuk menetapkan jawaban dan melanjutkan proses.

❯ Which package manager would you like to use? ● npm ○ pnpm ○ yarn ○ bun ○ deno

❯ Initialize git repository? ● Yes / ○ No

Tekan spasi untuk memilih dan tekan enter untuk menetapkan jawaban dan melanjutkan proses.

❯ Would you like to install any of the official modules? ◼ @nuxt/content – The file-based CMS with support for Markdown, YAML, JSON ◼ @nuxt/eslint – Project-aware, easy-to-use, extensible and future-proof ESLint integration ◼ @nuxt/fonts – Add custom web fonts with performance in mind ◼ @nuxt/icon – Icon module for Nuxt with 200,000+ ready to use icons from Iconify ◻ @nuxt/image – Add images with progressive processing, lazy-loading, resizing and providers support ◻ @nuxt/scripts – Add 3rd-party scripts without sacrificing performance ◻ @nuxt/test-utils – Test utilities for Nuxt ◼ @nuxt/ui – The Intuitive UI Library powered by Reka UI and Tailwind CSS

Setelah pemasangan selesai, buka proyek dengan penyunting teks atau IDE. Saya menggunakan Zed sebagai contoh.

# masuk ke direktori proyek
cd nuxtjs-blog

# buka dengan Zed
zed .

Mulai aplikasi menggunakan terminal bawaan dari Zed.

npm run dev

Buka peramban web dan pergi ke http://localhost:3000 untuk melihat aplikasi.

Mengatur Tailwind CSS

Selanjutnya adalah mengatur NuxtUI dan Tailwind CSS pada proyek kita. Kita akan menggunakan komponen Switch untuk membuat tombol saklar tema terang dan gelap agar pembaca bisa menentukan sendiri mana yang lebih nyaman baginya.

Pertama, mari kita buat berkas assets/css/main.css.

main.css
@import "tailwindcss";
@import "@nuxt/ui";

Kemudian tambahkan pengaturan css pada berkas nuxt.config.ts seperti berikut.

nuxt.config.ts
import tailwindcss from "@tailwindcss/vite";

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  compatibilityDate: "2024-11-01",
  devtools: { enabled: true },
  modules: ["@nuxt/content", "@nuxt/eslint", "@nuxt/fonts"],
  css: ["~/assets/css/main.css"],
});

Pasang ikon

Tombol saklar untuk tema memerlukan ikon untuk memberitahu kepada pembaca tema mana yang sedang aktif. NuxtUI menyarankan untuk memasang kumpulan ikon secara lokal agar dapat disajikan langsung dari komputer atau fungsi dari serverless. Kali ini kita akan menggunakan ikon dari lucide. Silakan baca dokumentasi ikon NuxtUI untuk informasi lebih lanjut jika Anda ingin menggunakan ikon yang lain.

npm install @iconify-json/lucide

Mengatur tata letak

Tombol saklar tema

Buat berkas components/ColorModeToggle.vue.

ColorModeToggle.vue
<script setup lang="ts">
const colorMode = useColorMode();

const isDarkMode = computed({
  get: () => colorMode.value === "dark",
  set: (_isDark) => (colorMode.preference = _isDark ? "dark" : "light"),
});
</script>

<template>
  <ClientOnly v-if="!colorMode?.forced">
    <div class="flex items-center">
      <UIcon name="i-lucide-sun" />
      <USwitch
        v-model="isDarkMode"
        class="mx-2"
        aria-label="color mode switch"
      />
      <UIcon name="i-lucide-moon" />
    </div>
  </ClientOnly>
</template>

Tata letak halaman

Karena kita hanya menggunakan tata letak tunggal untuk setiap halaman, kita akan menggunakan berkas app.vue sebagai berkas untuk tata letak. Lapisi komponen NuxtPage dengan komponen UApp untuk menerapkan gaya dari NuxtUI dan juga komponen UContainer untuk menetapkan lebar isi halaman. Lebar bawaan dari UContainer adalah sekitar 1200px dan mungkin Anda ingin mengubahnya agar sesuai dengan gaya yang Anda inginkan. Silakan baca dokumentasi komponen Container NuxtUI untuk keterangan lebih lanjut.

app.vue
<template>
  <div>
    <NuxtRouteAnnouncer />
    <NuxtLoadingIndicator />

    <UApp>
      <UContainer>
        <nav class="flex justify-between py-4">
          <NuxtLink to="/" class="decoration-dotted underline-offset-8 hover:underline hover:text-primary">
            Blog
          </NuxtLink>

          <ColorModeToggle />
        </nav>

        <NuxtPage />
      </UContainer>
    </UApp>
  </div>
</template>

Membuat halaman beranda

Tata letak sudah siap dan sekarang kita akan mengatur pengaturan isi sebelum menampilkan data di halaman kita. Nuxt akan mengolah semua jenis berkas yang berada di dalam direktori content dan kita menginginkan hanya berkas Markdown yang akan diolah. Buat berkas content.config.ts pada direktori utama proyek dengan kode sebagai berikut.

content.config.ts
import { defineCollection, defineContentConfig, z } from "@nuxt/content";

export default defineContentConfig({
  collections: {
    articles: defineCollection({
      type: "page",
      source: "**/*.md",
      schema: z.object({
        title: z.string(),
        date: z.string(),
      }),
    }),
  },
});

Di sini kita menggunakan articles sebagai nama kumpulan. Tentu saja kita dapat menggunakan kata lain sebagai nama kumpulan seperti posts atau notes. Karena kita akan menerbitkan artikel untuk blog, kita tetap menggunakan articles sebagai nama kumpulan.

Properti obyek schema harus hadir pada bagian front matter di dalam berkas Markdown sehingga kita dapat menggunakannya pada berkas Vue.

Baik, mari kita buat artikel pertama kita dengan berkas content/artikel-pertama.md.

artikel-pertama.md
---
title: Artikel pertama
date: 2025-05-06
---

# Pendahuluan

Blog ini dibangun menggunakan:

- [Nuxt](https://nuxt.com) 3
- [Nuxt Content](https://content.nuxt.com) v3.4.0
- [NuxtUI](https://ui.nuxt.com) v3.1.0

Anda dapat membuat beberapa berkas lainnya jika Anda menginginkannya.

Data kita sudah siap dan mari kita tampilkan di halaman beranda. Buat berkas pages/index.vue.

index.vue
<script setup lang="ts">
const { data: posts } = await useAsyncData("posts", () =>
  queryCollection("articles").order("date", "DESC").all(),
);
</script>

<template>
  <main>
    <h1 class="font-bold mb-8 text-3xl">Blog</h1>

    <article
      v-for="post in posts"
      :key="post.id"
      class="flex flex-col gap-2 mb-4"
    >
      <NuxtLink
        :to="{
          name: 'blog-slug',
          params: { slug: post.path.replace('/', '') },
        }"
        class="decoration-dotted text-2xl text-primary underline-offset-4 hover:underline"
      >
        {{ post.title }}
      </NuxtLink>
      <NuxtTime
        :datetime="post.date"
        year="numeric"
        month="long"
        day="numeric"
        locale="id"
        class="text-sm"
      />
    </article>
  </main>
</template>

Anda akan melihat daftar artikel yang berada di halaman beranda. Mungkin Anda perlu menyalakan ulang server untuk melihat perubahan.

Menampilkan halaman artikel

Ketika membuat halaman beranda, Anda mungkin akan melihat pesan galat bahwa halaman blog tidak tersedia atau semacamnya. Ini terjadi karena kita belum membuat halaman untuk menampilkan artikel. Pada proyek ini kita ingin URL ke artikel terlihat seperti /blog/artikel-pertama. Itulah mengapa properti name pada NuxtLink adalah blog-slug. Sedangkan slug adalah nama pola URL untuk artikel sehingga nanti kita akan membuat berkas bernama [slug].vue.

Mari kita buat berkas pages/blog/[slug].vue.

[slug].vue
<script setup lang="ts">
const slug = useRoute().params.slug;

const { data: article } = await useAsyncData(`article-${slug}`, () =>
  queryCollection("articles").path(`/${slug}`).first(),
);

useSeoMeta({
  title: article.value?.title + " | Blog",
});
</script>

<template>
  <main>
    <header v-if="article" class="mt-24 mb-12">
      <h1 class="font-bold mb-4 text-4xl">{{ article.title }}</h1>
      <NuxtTime
        :datetime="article.date"
        year="numeric"
        month="long"
        day="numeric"
        locale="id"
      />
    </header>

    <ContentRenderer v-if="article" :value="article" tag="article" />
    <div v-else>
      <h1 class="font-bold mb-8 text-4xl">Halaman tidak ditemukan</h1>
      <NuxtLink to="/">Kembali ke blog</NuxtLink>
    </div>
  </main>
</template>

Sekarang cobalah untuk menekan judul dari artikel. Seharusnya Anda akan diarahkan ke halaman artikel. Anda mungkin perlu menyalakan ulang server untuk melihat perubahan atau jika terjadi galat.

Mengatur gaya komponen Prose

Isi dari Markdown ditampilkan menggunakan komponen Prose dan bawaannya tidak memiliki gaya apapun. Awalnya terasa merepotkan dan seingat saya dulu Tailwind memiliki kumpulan gaya prose yang memudahkan. Dugaan saya mengapa Tailwind melakukan ini adalah untuk melepas ketergantungan dengan pihak, vendor, atau tata cara tertentu sehingga pengguna lebih leluasa dalam mengatur gaya.

Kita dapat mengubahsuai komponen-komponen Prose dengan membuat komponen dengan nama yang sama di dalam direktori /components/content. Salin sebarang komponen yang akan Anda gunakan dari @nuxtjs/mdc kemudian atur gaya sesuai dengan keinginan Anda.

Menambahkan favicon

Sebelum kita memasang blog kita, mari kita buat favicon untuk blog kita. Silakan buat favicon dari favicon.io dan salin isinya ke direktori public.

Tambahkan pengaturan app pada berkas nuxt.config.ts.

nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  app: {
    head: {
      title: "Blog",
      htmlAttrs: {
        lang: "en",
      },
      meta: [
        {
          name: "description",
          content: "Selamat datang di blog saya",
        },
      ],
      link: [
        {
          rel: "apple-touch-icon",
          sizes: "180x180",
          href: "/apple-touch-icon.png",
        },
        {
          rel: "icon",
          type: "image/png",
          sizes: "32x32",
          href: "/favicon-32x32.png",
        },
        {
          rel: "icon",
          type: "image/png",
          sizes: "16x16",
          href: "/favicon-16x16.png",
        },
        { rel: "manifest", href: "/site.webmanifest" },
      ],
    },
  },
});

Pemasangan

Sekarang blog kita siap untuk dipasang. Sebelumnya saya telah mencoba memasang blog ini ke beberapa penyedia layanan. Berikut adalah pengalaman saya memasang blog di Cloudflare dan GitLab Pages.

Cloudflare

Untuk pemasangan yang mudah dan tidak bertele-tele, Cloudflare Pages merupakan pilihan yang bagus. Memasang halaman statis di Clouflare Pages membuat situs web Anda dimuat lebih cepat karena CDN atau jaringan edge milik Cloudflare. Salah satu kekurangan dari pemasangan di Clouflare adalah Anda akan mendapatkan nama yang agak acak jika nama proyek Anda menggunakan nama umum, misalnya blog-s92jws.pages.dev. Anda perlu berlangganan paket Business untuk menggunakan domain ubahsuai jika tidak menggunakan pengelolaan domain milik Clouflare.

Sebelum melanjutkan pemasangan ke Clouflare, tambahkan pengaturan nitro pada berkas nuxt.config.ts untuk mencocokkan perutean milik Clouflare.

export default defineNuxtConfig({
  nitro: {
    prerender: {
      autoSubfolderIndex: false
    }
  }
})

Ketika memasang ke Clouflare, versi Node.js bawaannya masih menggunakan versi 18 LTS ketika tulisan ini dibuat (Mei 2025) dan proyek ini menggunakan Node.js versi 22 LTS. Atur versi Node.js yang akan digunakan dengan menetapkan variabel lingkungan.

NODE_VERSION=22.15.0

Karena situs web yang akan kita bangun bersifat statis, maka kita perlu mengubah perintah untuk build.

FieldValue
Build commandnpm run generate
Publish directorydist

Setelah pemasangan selesai, lihat situs web Anda pada URL yang disediakan oleh Clouflare.

GitLab Pages

Alternatif dari Clouflare Pages adalah GitLab Pages. Situs web Anda mungkin tidak akan dimuat secepat ketika dipasang di Clouflare (masih bisa diterima, tidak terlalu cepat atau terlalu lamban) dan Anda bisa menggunakan domain ubahsuai secara gratis sekaligus sertifikat SSLnya!

Proses pemasangannya cukup sederhana, petunjuk yang disampaikan di dokumentasi GitLab Pages dan Nuxt juga jelas. Pastikan Anda sudah mencocokkan pengaturan dari kedua sumber tersebut.

Setelah selesai pemasangan, GitLab akan menambahkan sebuah berkas gitlab-ci.yml di repositori Anda. Tarik perubahan dari repositori ke komputer Anda untuk menghindari konflik pada kegiatan Git selanjutnya.

Untuk meningkatkan performa situs web dan proses build, perbarui berkas gitlab-ci.yml hingga seperti kode berikut.

gitlab-ci.yml
# The Docker image that will be used to build your app
image: node:lts
create-pages:
  pages:
    # The folder that contains the files to be exposed at the Page URL
    publish: .output/public
  rules:
    # This ensures that only pushes to the default branch will trigger
    # a pages deploy
    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
  cache: # Cache modules using lock file
    key:
      files:
        - package-lock.json
    paths:
      - .npm/
  # Functions that should be executed before the build script is run
  before_script:
    - npm ci --cache .npm --prefer-offline
  script:
    - npm run generate
    - find .output/public -type f -regex '.*\.\(htm\|html\|xml\|txt\|text\|js\|css\|svg\)$' -exec gzip -f -k {} \;
    - find .output/public -type f -regex '.*\.\(htm\|html\|xml\|txt\|text\|js\|css\|svg\)$' -exec brotli -f -k {} \;

Kita menambahkan pengaturan cache untuk mempercepat proses build dan mengompres aset menggunakan gzip dan brotli untuk mempercepat pengunduhan sehingga situs Anda dimuat lebih cepat.

Kesimpulan

Jika semuanya berjalan dengan lancar, alhamdulillah. Kita sudah berhasil membuat blog menggunakan Nuxt. Kita sudah membangun kemampuan dasar seperti pengelolaan isi dan selebihnya gunakan kreatifitas Anda untuk membuat tampilan web yang menarik dan terasa personal!

Setelah situs web Anda jadi, coba juga ukur kinerjanya menggunakan PageSpeed Insights. Dapatkah Anda mendapat nilai yang sempurna?

Jika Anda memerlukan inspirasi, silakan intip repositori situs web ini.

Daftar pustaka

Assalaamu 'alaikum

Saya Findra. Blog ini adalah catatan perjalanan saya menjelajahi serunya coding, dunia open source yang luas, dan dinamika pengembangan web. Di sini, Anda akan menemukan tutorial, berita, refleksi pribadi, serta pameran portofolio dan hobi saya.