Mari mengawali tulisan ini dengan permintaan maaf. Saya ingin memohon maaf kepada para pengunjung website blog ini juga kepada orang-orang yang saya share link-nya di media sosial. Maaf karena ada error yang terjadi saat membuka post di blog ini. Error tersebut belum saya sadari dan temukan saat saya menerbitkan situs ini sampai beberapa waktu lalu.
Selain itu, mungkin ada sedikit keharusan saya menuliskannya di sini juga, saya minta maaf pada diri saya sendiri. Maaf karena telah mempermalukan diri sendiri di hadapan orang banyak. Membagikan link website yang error.
Okey, semoga dimaafkan, hehehe. Selanjutanya, monggo kita bahas tentang error tersebut. Kita akan membahas dari awal saya menemukan error ini, mencoba mencari-cari dari mana asalnya, memahami apa yang salah dan kurang, hingga solusinya. Jadi bisa dikatakan tulisan ini adalah dokumentasi proses belajar saya mengenai infrastruktur situs web ini secara lebih mendalam (Nuxt, MongoDB Atlas, serta serverless function).
Kok Error Ya?
Awal saya menyadari adanya error yaitu saat saya iseng membuka salah satu post secara langsung tanpa terlebih dahulu membuka halaman home atau all posts (daftar semua post). Betapa kagetnya saya saat saya mendapatkan tampilan error seperti gambar di bawah ini.
Kalian ketemu juga error ini?
Saya lalu memuat ulang (reload) halaman error ini dan ternyata error-nya hilang sehingga halaman yang dimaksud bisa dibuka. Saya coba ke halaman-halaman yang lain, tidak ada masalah. Saya lalu coba membuka halaman post dengan copy-paste link address dan error yang sama kembali muncul. Jadi yang jelas, error ini hanya muncul saat pengunjung mencoba mengakses halaman post (alias halaman yang memuat tulisan blog) dengan menggunakan link secara langsung.
Mencari Sumber Error (Sambil Mari Mengingat Kembali Juga)
Saya lalu masuk ke Visual Studio Code, buka terminal, dan menjalankan kode website di laptop saya alias secara lokal. Ternyata error-nya masih ada. Jadi, error ini memang salah saya, bukan masalah Netlify sebagai tempat hosting frontend atau MongoBD Atlas sebagai tempat database dan severless function. Lebih jelasnya, letak kesalahannya ada di kode-kode yang saya tulis.
Seperti web dev yang lain di era sekarang, saya langsung buka Development Tools di browser dan buka console. Di sana terdapat pesan error berupa 'Hydration node missmatch'. Saya lalu googling dan buka situs sana-sini. Dari sana yang saya dapatkan adalah, missmatch terjadi karena hasil render dari server side rendering (SSR) berbeda dengan hasil render dan client yang terjadi saat proses hydration.
Pesan missmatch Nuxt di browser console saat menjalankan web secara lokal
Okey ada baiknya kita mengingat kembali tentang sistem frontend yang saya gunakan. Jadi di situs ini, saya menggunakan Nuxt, sebuah frontend framework yang sedang populer belakangan ini.
Sebelumnya, mengapa pembuat situs web perlu frontend framework? Agar proses pembuatan dan pengembangan situs web (atau aplikasi web) bisa lebih mudah dan cepat jika dibandingkan cara konvensional dengan menulis dari awal file HTML, CSS, dan JavaScript. Selanjutnya, dengan fitur-fitur yang ada di frontend framework, pengembang juga bisa lebih fokus untuk membuat situs yang bagus, interaktif, dan berkesan bagi pengunjung.
Nuxt sendiri mungkin setara dengan Next, frontend framework lain yang bisa dikatakan lebih populer. Bedanya, Nuxt berbasis Vue.js, sedangkan Next berbasis React.js. Nah, bedanya Nuxt dengan Vue yaitu Nuxt lebih banyak konfigurasi dan tools bawaan pertama kali kita instal. Jadi lebih cepat dan lebih mudah lagi untuk membuat situs yang layak terbit dibanding Vue. Salah satu konfigurasi tersebut adalah rendering modes. Kalau Nuxt, sejak awal sudah support dengan beberapa rendering strategy yang sedang tren. Nah, yang paling ngetren dan saya gunakan untuk situs ini adalah SSR + Hydration atau dalam situs resminya Nuxt disebut Universal Rendering.
Penjelasan selanjutnya cepat saja kali ya, karena sudah pernah saya bahas di tulisan saya sebelumnya. Jadi sekedar sekilas pandang saja. SSR adalah proses pembuatan halaman web yang dilakukan di server. Jadi saat browser atau client meminta suatu halaman situs, maka server akan terlebih dahulu mengolah data di database dan program frontend framework hingga menghasilkan file HTML, CSS, dan JavaScript utuh. File-file inilah yang bisa dimuat browser. Ya ini mirip dengan proses yang terjadi di situs-situs berbasis PHP misalnya Wordpress. Hydration sendiri adalah proses di sisi client untuk membuat hasil SSR ini berubah. Yang tadinya hanya static menjadi aplikasi interaktif yang biasa ada di Vue.js alias menjadi mirip single page application (SPA). Mengapa perlu konsep yang rumit begitu? Karena kita ingin medapat kelebihan dari keduanya. SSR bisa membuat search engine optimization (SEO) lebih baik, sedangkan SPA bisa membuat pengalaman pengguna lebih baik.
Sekarang, mari kembali ke masalah error yang saya hadapi. 'Hydration node missmatch', seperti yang tadi saya sebutkan, merupakan indikasi adanya perbedaan antara render di server dan client. Kalau saya baca-baca, hal ini terjadi karena banyak faktor. Bisa karena ada elemen yang berisi informasi waktu sekarang (missmatch terjadi karena waktu render di server pasti terjadi sebelum render di client), fetch data ke pihak ketiga yang punya kebijakan berbeda antara server dan client (misalnya untuk keperluan analisis), atau Vue komponen yang kurang optimal (biasanya v-if atau v-show). Kalau di artikel internet, pesan missmatch di console itu biasanya jelas, apa yang berbeda antara render server dan client. Namun, pesan yang saya dapat tidak jelas. Apanya yang salah?
Kebetulan saat itu saya baru saja mengubah beberapa elemen agar tampilan lebih bagus. Saya ubah tata letak daftar di halaman all post dan menambahkan fitur reply admin di halaman post (tempat artikel ditampilkan). Nah, saya curiga ada masalah saat saya membuat fitur reply admin, karena yang error hanya di bagian halaman post.
Fitur Reply Admin yang baru saja saya gunakan di post lain
Apa yang saya lakukan kemudian? Saya coba menjalankan kode website tanpa bagian-bagian yang saya curigai bermasalah. Reply admin saya hapus, tidak ada efeknya. Elemen-elemen v-if saya hapus, tidak ada efeknya juga. Elemen v-show kali ini, juga tidak ada bedanya. Lalu saya kepikiran masalah library Marked yang saya gunakan. Library ini berfungsi untuk mengubah teks markdown menjadi HTML. Saya menggunakan library ini karena saya lebih suka menulis dengan format markdown. Jadi library ini saya pasang di kode frontend bersama dengan Nuxt, dan saya aktifkan di halaman post. Saya lalu coba menghilangkan Marked ini, tapi error masih tetap ada. Saya benar-benar bingung. Saya baca ulang artikel-artikel di internet mengenai hydration missmatch, tapi tidak ada yang kelewatan. Saya cari artikel-artikel lain, tidak ada yang memberi jawaban.
Akhirnya, petunjuk saya dapatkan secara tiba-tiba.
Menemukan Titik Terang
Saat saya membuka halaman all posts, sebenarnya ada juga masalah missmatch ini. Kebetulan saya menggunakan kode CSS untuk jenis font yang bermasalah dengan proses rendering. Namun, ketidakcocokan ini tidak membuat halaman tersebut menjadi error sebagaimana halaman post. Karena itu, saya mulai berpikir bahwa missmatch ini tidak ada kaitannya dengan error yang saya hadapi. Well setelah tahu masalahnya, sebenarnya ada sih kaitannya, tapi tidak secara langsung.
Lalu apa masalahnya? Saya berlogika. Jika yang error hanya ada di halaman post, maka pasti fitur yang hanya ada di sanalah penyebabnya. Apakah fitur tersebut? Sebenarnya sudah jelas sejak awal, fitur tersebut adalah penghitung visitor atau penghitung jumlah kunjungan. Saya lalu coba menonaktifkan fitur ini, dan error langsung hilang. Saya merasa agak bodoh jadinya. Seharusnya itu hal sepele dan mudah dipahami, tapi saya perlu waktu lama untuk menemukannya. Saat menulis tulisan ini, yang bisa saya katakan pada diri saya sendiri adalah wajar kalau saya tidak langsung paham karena terlalu fokus dengan masalah missmatch dan UI yang baru saja saya ubah. Semoga bukan pembelaan tidak berarti.
Okey mari kita bahas tentang penghitung visitor versi saya ini. Jadi, sistem ini bertugas untuk menghitung berapa jumlah pengunjung pada setiap post. Pengunjung di sini punya batasan waktu, yaitu 24 jam. Jadi, jika perangkat yang sama dengan browser yang sama membuka post yang sama dalam 24 jam terakhir, maka hanya dihitung satu kunjungan. Ya itulah sistem yang saya inginkan. Untuk itu, sejak pertama kali membuka post, perlu ada yang mengingat sampai 24 jam.
Di situs ini, saya menggunakan browser pengunjung sendiri untuk mengingatnya. Jadi saya menggunakan cookies untuk menandai setiap kunjungan pada setiap halaman post. Cookies sendiri adalah salah satu sarana penyimpanan data di browser. Saya memutuskan untuk menggunakannya karena sifat cookies yang dikirim ke server bersama request dan dan masa kadaluarsa untuk setiap data yang tersimpan. Sifat yang pertama menurut saya cocok karena saya perlu tahu sejak awal apakah browser sudah pernah berkunjung atau belum, dan di awal berarti kita bicara SSR alias server. Sifat yang kedua jelas cocok karena saya ingin batasan waktu 24 jam. Cookies dapat dengan mudah diatur waktu kadaluarsanya, dengan maksimal (sampai waktu saya menulis tulisan ini) mencapai 400 hari.
Konsepnya adalah jika tidak ada cookies, berarti ini kunjungan yang pertama selama 24 jam dan perlu dihitung. Lalu sistem akan mengeset cookies pada browser. Sehingga pada kunjungan selanjutnya, cookies ini akan menjadi tanda bahwa ini bukan kunjungan pertama dan tidak perlu dihitung. Proses penghitungan dilakukan bersamaan dengan fetch data ke database. Setiap akan menampilkan artikel halaman post, maka entah itu server atau client akan mengambil data dari MongoDB function. Proses pengambilan data ini menggunakan HTTP. Nah, jika ada penghitungan pengunjung, maka saya membuat sebuah query parameter dalam alamat HTTP-nya. Function di MongoDB sudah saya tulis sedemikian sehingga bisa membaca query parameter tersebut. Setelah itu, function ini akan melakukan penambahan data kunjungan pada post yang dimaksud. Jika tidak ada penghitungan pengunjung, maka tidak ada query parameter, hanya alamat HTTP yang aslinya saja.
Mengapa ini menjadi error? Untuk lebih jelasnya, mari kita runut satu per satu apa yang terjadi.
- Saat situs ini diakses, maka client akan meminta dokumen halaman ke server.
- Khusus untuk halaman post, maka server akan membaca cookies yang tersimpan sebelumnya di client yang dikirim bersamaan dengan permintaan tersebut. Jika pertama kali diakses dalam 24 jam, maka cookies tidak ada.
- Jika cookies tidak ada, maka server akan melakukan fetch ke database MongoDB Atlas dengan query paramaeter. MongoDB lalu mencari data, menambah jumlah visitor, dan mengirim data ke server.
- Setelah mendapat data, server lalu melakukan rendering.
- Halaman jadi lalu dikirimkan dan diterima client. Cookies juga diset.
- Hydration terjadi, Nuxt membandingkan rendering server dengan client.
- Karena cookies sudah diset, maka Nuxt menemukan bahwa alamat fetch untuk data post berubah (tidak lagi ada query parameter). Nah, Nuxt akan membandingkan apapun yang berkaitan dengan tampilan, entah itu data maupun gaya. Karena itu, Nuxt menganggap perbedaan alamat fetch itu bisa saja menyebabkan perbedaan data juga.
- Hal ini menyebabkan client mengulang proses yang terjadi di server, yaitu fetch data dan rendering. Hanya saja, entah mengapa dengan Nuxt, client tidak bisa melakukan fetch ulang. Saat saya mengecek MongoDB Atlas, aktivitas fetch tercatat di log, menunjukkan bahwa fetch benar-benar terjadi. Namun, client tidak menunjukkan aktivitas tersebut. Padahal jenis-jenis perintah lain mampu dijalankan oleh client dalam kondisi ini, misal
console.logyang saya gunakan untuk melihat apa yang terjadi. Sampai sekarang saya tidak tahu penyebab masalah ini. Dugaan saya mungkin ada hubungannya dengan await (JavaScript Promise) yang perlu digunakan untuk fetch. - Data tidak ada, maka error muncul.
Alur terjadinya error
Apa yang Saya Lakukan Kemudian
Saya orang yang 'bingungan', maka saya jadi bingung. Sekalipun saya (hampir sepenuhnya) tahu masalahnya, tapi saya tidak tahu harus bagaimana untuk menyelesaikannya. Karena itu, saya sempat menghentikan fitur penghitung visitor yang ada. Apakah saya harus menghilangkan fitur ini? Saya rasa sekalipun ini bukan fitur yang canggih dan akurat, tapi informasi kunjungan tetap perlu. Minimal sebagai tanda, tulisan mana yang banyak dikunjungi dan tulisan mana yang tidak. Karena itu saya coba cari-cari konsep lain untuk menghitung kunjungan.
Di internet, banyak artikel yang memuat tentang sistem penghitungan visitor yang dilakukan di sisi backend. Kalau saya pikir-pikir, ya memang lebih logis. Konsepnya sebenarnya tidak terlalu rumit, tapi memang kalau dibandingkan yang sisi frontend alias client, ya ini sedikit lebih repot. Secara umum, HTTP request yang dilakukan di internet akan saling mempertukarkan data termasuk IP address. Nah, IP address ini yang dapat digunakan untuk mengingat client. Backend dapat menyimpan data ini dalam database, meski sebelumnya perlu enkripsi dulu karena merupakan informasi sensitif. Kemudian, penghapusan data IP address ini juga ditangani oleh backend.
Okey, setelah dipikir-pikir, konsep ini sepertinya bisa diimplementasikan. Nuxt saya rasa bisa mendapat IP address ini sekalipun saya masih harus cari-cari dulu di internet. Setelah itu IP address yang didapat bisa dikirimkan ke MongoDB bersamaan dengan fetch. MongoDB Atlas sendiri punya fitur untuk menghapus data secara otomatis berbasis waktu menggunakan time to live (TTL) indexes.
Namun, saya kemudian kepikiran cara lain. Masalah di sistem sebelumnya adalah alamat fetch data yang berbeda antara server dan client. Perbedaannya adalah di query parameter. Karena itu, saya kepikiran untuk memisahkan antara fetch data dan request untuk penghitung visitor. Jadi alamat fetch data ya sudah, yang biasa saja, tidak perlu ada query atau tambahan lain. Sehingga alamat fetch bisa konsisten dan tidak berubah-ubah. Untuk ini, saya pakai sistem sebelumnya cuma dikurangi bagian-bagian untuk penghitung visitor. Saya lalu buat sistem HTTP request lain beserta function-nya di MongoDB Atlas yang khusus menangani penghitungan visitor.
Sistem baru ini bekerja dengan kondisi yang sama dengan sebelumnya. Ia akan mengirimkan tanda ke MongoDB Atlas jika tidak ada cookies, yang berarti ini adalah kujungan pertama dalam 24 jam. Sedangkan jika ada cookies, maka sistem ini tidak berjalan sama sekali. Yang menyebabkan sistem ini masuk akal yakni sistem ini tidak ada hubungannya dengan tampilan. Jadi apakah sistem ini bekerja atau tidak, punya alamat yang berbeda atau sama, apapun itu tidak ada kaitannya dengan data ataupun gaya sebagai bahan rendering.
Setelah saya coba, ternyata sistem ini bisa bekerja. Akhirnya ya sudah, cara ini yang saya gunakan. Pertimbangan saya adalah sistem berbasis backend akan membebani backend, padahal backend di situs Naftena ini menggunakan serverless function milik MongoDB Atlas. Benda ini punya paket-paket harga, dan sampai sekarang saya masih gratis karena masih di bawah batasan tertentu. Intinya saya pakai versi promonya 'lah. Karena itu saya perlu membuat sistem yang sesederhana mungkin agar tetap gratis. Selain itu, sistem terpisah ini juga bisa bekerja sesuai apa yang saya inginkan. Masalah cookies juga tidak ada regulasinya di negara ini, jadi aman. Akhirnya, jika nanti ada masalah, saya merasa hal itu bisa dijadikan bahan belajar lagi. Lagi pula, sistem berbasis backend juga sudah ada di bayangan. Jika memang perlu, sistem backend bisa dicoba.
Penutup
Ya kira-kira itulah yang bisa saya bagikan ke pembaca sekalian untuk sekarang ini. Semoga bermanfaat ya. Saya tidak terlalu yakin sih, hehehe. Penjelasan saya mungkin agak aneh dan tidak jelas. Ya minimal untuk saya manfaatnya. Mohon maaf jika ada salah-salah kata atau salah tulis. Pembaca budiman silakan kalau mau koreksi. Selain itu, saya lumayan tertarik dengan masalah Nuxt dan berbagai sistem di internet, jadi boleh-boleh saja kalau mau memberi saran, bertanya, atau berdiskusi dengan saya.
Okey, seperti itu dulu saja, bye for now!
