
  <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
      <title>Nico&#39;s Blog</title>
      <link>https://www.nico.fyi/blog</link>
      <description></description>
      <language>en-us</language>
      <managingEditor>hi@nico.fyi (Nico Prananta)</managingEditor>
      <webMaster>hi@nico.fyi (Nico Prananta)</webMaster>
      <lastBuildDate>Mon, 10 Nov 2025 00:00:00 GMT</lastBuildDate>
      <atom:link href="https://www.nico.fyi/feed.xml" rel="self" type="application/rss+xml"/>
      
  <item>
    <guid>https://www.nico.fyi/blog/validate-http-request-with-zod</guid>
    <title>Validate HTTP request with Zod</title>
    <link>https://www.nico.fyi/blog/validate-http-request-with-zod</link>
    <description>
I got tired of writing if-else statements to validate the HTTP request like this:

```tsx:route.ts showLineNumbers
export const GET = async (request: Request) =&gt; {
  const { searchParams } = new URL(request.url)
  const name = searchParams.get(&#39;name&#39;)
  if (!name) {
    return NextResponse.json({ error: &#39;Name is required&#39; }, { status: 400 })
  }
  return NextResponse.json({ name })
}
```

It got annoying fast when there are more parameters, headers, and body to validate. Multiply that by the number of routes and you&#39;ll end up with a lot of repetitive code.

So I made [zod-request](https://github.com/nicnocquee/zod-request), a library that allows you to validate the HTTP request with Zod.

Here&#39;s an example of how to use it in a Next.js Route handler:

```tsx:route.ts showLineNumbers
import { z } from &quot;zod&quot;;
import {
  requestSchema,
  searchParamsSchema,
  bodySchema,
  headersSchema,
  httpMethodSchema,
} from &quot;@nicnocquee/zod-request&quot;;

// Create the schema for the request
const sch</description>
    <pubDate>Mon, 10 Nov 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category><category>zod</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/quick-look-use-effect-event</guid>
    <title>Quick look into the useEffectEvent</title>
    <link>https://www.nico.fyi/blog/quick-look-use-effect-event</link>
    <description>
React just released the new version 19.2.0 which brings several new features including the new [useEffectEvent](https://react.dev/reference/react/useEffectEvent) hook. The doc says that it is a React Hook that lets you extract non-reactive logic from your Effects into a reusable function called an Effect Event.

Thanks to a quick chat with [Dan Abramov](https://bsky.app/profile/nico.fyi/post/3m27vhlzno22y), I got to realize that my understanding of the hook was initially incorrect.

At first I thought it&#39;s a way to create a &quot;stable&quot; function that always &quot;sees&quot; the latest state and props. But Dan explained that it actually creates an &quot;unstable&quot; function. It is basically a way to create a function that

- always &quot;sees&quot; the latest state and props
- doesn&#39;t cause an Effect that contains the function to re-run even though the function is &quot;unstable&quot;

Let&#39;s take a look at this example below which I adopted from the React doc. We have a `notify` function (line 32-34) that is created by wrappi</description>
    <pubDate>Thu, 02 Oct 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/new-feature-for-use-resettable-action-state</guid>
    <title>Cancel action or modify payload before useActionState</title>
    <link>https://www.nico.fyi/blog/new-feature-for-use-resettable-action-state</link>
    <description>
Just updated my open source React hook [useResettableActionState](https://github.com/nicnocquee/use-resettable-action-state). At the beginning, I made it to allow me to [reset the state after submitting the form using useActionState](/blog/reset-state-from-react-useactionstate). Now it also allows you to cancel the action or modify the payload before calling the server action.

## Cancelling the action

Before submitting a form, you may want to cancel the action if the entered values are not valid. For example, when updating a user&#39;s password, you may want to cancel the action if the user fails to repeat the new password correctly. In this case, you can provide a function to the new `beforeAction` argument. Code example:

```tsx:app/page.tsx showLineNumbers {10-15}
&#39;use client&#39;;
import { doSomething } from &#39;./actions&#39;;
import { useResettableActionState } from &#39;use-resettable-action-state&#39;;

export default function Form({ initialState }: { initialState: { password: string | null, error</description>
    <pubDate>Sun, 24 Aug 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>typescript</category><category>open source</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/taming-react-component</guid>
    <title>Taming React Component</title>
    <link>https://www.nico.fyi/blog/taming-react-component</link>
    <description>
As a project grows, the codebase inevitably becomes more complex. This is where the concept of clean code comes in. Clean code is a set of principles and practices that help developers write code that is easy to understand, maintain, and extend.

In React, the complexity of a component is often caused by these factors: the number of props, the number of states, and the number of effects. In this post, I&#39;ll focus on the issue of states and effects.

## React&#39;s Two Towers

I&#39;m pretty sure this is not a new concept, but I&#39;ve come to realize that a React component can be seen as having two parts: **the user interface and the business logic**. Consider the following simple component:

```tsx:component.tsx showLineNumbers {4-13, 16-21}
import { useState, useEffect } from &#39;react&#39;

const Component = () =&gt; {
  const [count, setCount] = useState(0)
  const [name, setName] = useState(&#39;&#39;)

  useEffect(() =&gt; {
    console.log(&#39;count&#39;, count)
  }, [count])

  useEffect(() =&gt; {
    console.log(&#39;name</description>
    <pubDate>Sun, 17 Aug 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-use-multiple-prisma-schema-in-nextjs-monorepo</guid>
    <title>How to use multiple Prisma schemas in a Next.js monorepo</title>
    <link>https://www.nico.fyi/blog/how-to-use-multiple-prisma-schema-in-nextjs-monorepo</link>
    <description>
Monorepos have come a long way since the days of Lerna. When I tried them before, it was painful. Lots of things were not working as expected. But now, it&#39;s a lot better. I&#39;ve been using [Turborepo](https://turborepo.com) for a while now and it&#39;s been great. It got me to the point where I always want to start a new project with it.

However, there are still some things that don&#39;t just work. One of those things is having [Prisma as a shared package in a monorepo](https://www.prisma.io/docs/guides/turborepo#1-create-your-monorepo-using-turborepo). When a Next.js app in the monorepo uses the shared Prisma package, you&#39;ll run into runtime errors when deployed to serverless environments like Vercel or Netlify.

![Netlify Prisma Monorepo Next Error](/static/images/articles/netlify-prisma-monorepo-next-error.png)

```
This is likely caused by a bundler that has not copied
&quot;libquery_engine-rhel-openss1-3.0.x.so.node&quot; next to the resulting bundle.
Ensure that &quot;libquery_engine-rhel-openss1-3.0.</description>
    <pubDate>Mon, 21 Jul 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>typescript</category><category>prisma</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/dataqueue-dev-background-job-queue-postgresql-typescript</guid>
    <title>Introducing DataQueue</title>
    <link>https://www.nico.fyi/blog/dataqueue-dev-background-job-queue-postgresql-typescript</link>
    <description>
There are so many ways to run a long-running process in the background when you use modern React frameworks like Next.js, Remix, etc. You can spin your own Redis and use BullMQ as shown in [this guest post](/blog/long-running-jobs-nextjs-redis-bull), or use a managed service like Trigger.dev. But that means adding additional technology stack to your project and, more often than not, you need to pay for the service. Even rolling your own Redis is not free because you need to pay for the storage.

In my projects at the company where I work, we needed to defer a relatively long-running task to a background job to keep the user experience smooth. Since we already use PostgreSQL for our database, I decided to use it as the storage for the background job queue.

So I made an open source library called [DataQueue](https://dataqueue.dev) to handle this. It&#39;s a background job queue for Node.js and PostgreSQL with type-safe support with TypeScript.

![](https://docs.dataqueue.dev/_next/image?ur</description>
    <pubDate>Tue, 15 Jul 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>opensource</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-test-react-server-component</guid>
    <title>How to test React Server Component</title>
    <link>https://www.nico.fyi/blog/how-to-test-react-server-component</link>
    <description>
Ever since Next.js supported React Server Components, I&#39;ve been using them in my projects. I really enjoy using them because it just makes sense. Every component that needs data from the server doesn&#39;t need to fetch the data from the browser. Less code, less complexity, fewer bugs.

But even after years of being released to the public, neither the Next.js team nor the React team has provided a way to test React Server Components. Not even LLMs know how to do it, which is understandable because they cannot actually think. But luckily, Next.js and React are open source, and I wasn&#39;t the only one who wanted to test React Server Components.

I found this [single gist](https://gist.github.com/sroebert/a04ca6e0232a4a60bc50d7f164f101f6) by [Steven Robert](https://gist.github.com/sroebert) that can be used to test React Server Components. It&#39;s basically this:

```typescript
// From https://gist.github.com/sroebert/a04ca6e0232a4a60bc50d7f164f101f6

import type { PropsWithChildren, ReactElement</description>
    <pubDate>Sun, 06 Jul 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-take-screenshots-in-github-actions-workflow</guid>
    <title>How to take screenshots of your statically exported Next.js site in GitHub Actions workflow</title>
    <link>https://www.nico.fyi/blog/how-to-take-screenshots-in-github-actions-workflow</link>
    <description>
Our [company&#39;s website](https://hyperjump.tech/en?utm_source=nico.fyi&amp;utm_medium=blog) is hosted in [GitHub pages](https://github.com/hyperjumptech/hyperjump.tech). It is a statically exported Next.js app. We still do pull requests and code reviews. But the problem is we don&#39;t have the luxury of having preview deployments like when we use Vercel or Netlify. Nevertheless, we still want to be able to see how the website looks like before merging a pull request.

So I added a GitHub Actions workflow to take screenshots of the website. It is a simple workflow that runs on every pull request.

First we need a script to find all pages after the Next.js app is built, then we use [Puppeteer](https://pptr.dev) to take screenshots of each page.

```javascript:take-screenshots.js showLineNumbers {18, 51-69, 108}
const puppeteer = require(&quot;puppeteer&quot;);
const fs = require(&quot;fs&quot;);
const path = require(&quot;path&quot;);

const OUT_DIR = path.join(process.cwd(), &quot;screenshots&quot;);
const SERVER_URL = &quot;http://local</description>
    <pubDate>Tue, 17 Jun 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>typescript</category><category>github</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/stop-mocking-prisma-in-tests</guid>
    <title>Stop mocking Prisma in tests - Part 1</title>
    <link>https://www.nico.fyi/blog/stop-mocking-prisma-in-tests</link>
    <description>
One of my pet peeves with testing in web development is mocking. So many devs tend to be purists who insist that the unit under test should be the only thing being tested, and that&#39;s why they mock everything. In projects that use Prisma or any other ORM, developers mock the ORM client when the function under test needs database access.

Let&#39;s take a look at a simple example: a login process. We may have a function like this:

```typescript:login.ts
export const login = async (email: string, password: string) =&gt; {
  const user = await prisma.user.findUnique({
    where: { email },
  })

  if (!user) {
    throw new Error(&#39;User not found&#39;)
  }

  // In real life, we would compare the stored hashed password with the provided password&#39;s hash
  if (user.password !== password) {
    throw new Error(&#39;Invalid password&#39;)
  }

  return user
}
```

A test for this function might look like this:

```typescript:login.test.ts showLineNumbers {6-15, 34, 40-42}
// src/auth/login.test.ts
import { desc</description>
    <pubDate>Mon, 16 Jun 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/multiple-async-local-storage</guid>
    <title>Multiple Async Local Storage</title>
    <link>https://www.nico.fyi/blog/multiple-async-local-storage</link>
    <description>
In the last post, I wrote about [Async Local Storage](/blog/async-local-storage-to-prevent-props-drilling) and how it can be used to prevent prop drilling in Next.js Route Handlers because it acts like context in React. In React, you can have multiple contexts to keep the code modular instead of having a single huge context. With a little bit of a trick, it can also be done in async local storage.

Let&#39;s say we have a route handler that can accept a `sort` query parameter and an `id` path parameter. These `sort` and `id` parameters are used in some functions to fetch data from a database. In Next.js, the search parameters and path parameters are only exposed in the route handler. Using async local storage, we can create a context for the search parameters and path parameters, and make it available to any functions that need them.

In the previous post, we already created a context for the user. Now, we will create a context for the request, which is very similar to the user context.

</description>
    <pubDate>Sun, 15 Jun 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/async-local-storage-to-prevent-props-drilling</guid>
    <title>Use Async Local Storage to prevent props drilling in Next.js Route handlers</title>
    <link>https://www.nico.fyi/blog/async-local-storage-to-prevent-props-drilling</link>
    <description>
When a private API endpoint is called, the handler should check if the request came from an authorized user before doing anything else. Usually the function that checks the request returns a user object if the request is authorized. Then this user object can be used by other functions in the handler.

When there are multiple functions in the handler, passing the user object as a parameter to each function is tedious. Not to mention when there are nested functions that need to access the user object. This props drilling is already cumbersome with just single object. Imagine when there are multiple objects that need to be passed around.

This is where [Async Local Storage](https://nodejs.org/api/async_context.html) comes in. It&#39;s like React Context but for Node.js functions. It allows you to store data in a &quot;store&quot; that is accessible to all functions, callbacks, and promise chains within the same execution context.

It&#39;s relatively easy to use. First we create a new Async Local Storage </description>
    <pubDate>Mon, 09 Jun 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/handle-date-time-correctly-in-your-fullstack-js-web-app</guid>
    <title>How to handle date and time correctly in your fullstack JS web app</title>
    <link>https://www.nico.fyi/blog/handle-date-time-correctly-in-your-fullstack-js-web-app</link>
    <description>
Handling date and time correctly in your fullstack JS web app is pretty tricky and can cause a subtle bug.

## The problem

This just happened to me. I got a report from my team mate that our website displayed the wrong date and time. In our backend, the admin who resides in the Switzerland entered June 18th, 2025 and time 22:00 from our dashboard. But on the website where the date was displayed, it was displayed as May 19th, 2025.

The bug was in the API end point where the date and time was stored in the database in UTC, i.e., 2025-06-18T22:00:00Z. Which is wrong because the admin intended to enter June 18th, 2025 at 22:00 in the local time of Switzerland. When another API end point was called to get the date and time, it was displayed and formatted using the user&#39;s browser&#39;s timezone. This is why the date was displayed as May 19th, 2025 when the user is in Switzerland.

## The solution

There are two ways to handle this. I can either convert the date and time to UTC in the user&#39;s b</description>
    <pubDate>Sat, 07 Jun 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/is-vibe-coding-the-future</guid>
    <title>Is Vibe Coding the Future?</title>
    <link>https://www.nico.fyi/blog/is-vibe-coding-the-future</link>
    <description>
I recently stumbled upon the term &quot;vibe coding&quot; through TikTok videos. It refers to using AI tools to generate code and drag-and-drop automation tools to handle business logic in apps.

This trend leaves me with mixed feelings. While it&#39;s impressive to see people rapidly creating apps and generating significant income, **it&#39;s concerning that many don&#39;t understand the underlying code**. Tools like Cursor, Copilot, Supermaven, and N8N have made app creation accessible to anyone, regardless of coding knowledge.

As an experienced developer, I find myself hesitant to fully embrace these AI tools because I&#39;m particular about code quality. My experiments with AI app builders like v0 and Bolt.new often result in code that doesn&#39;t meet my standards and contains TypeScript errors requiring manual fixes. Meanwhile, those new to development seem unbothered by these issues as long as their apps function. Some even claimed that they have made tens of thousands of dollars from the apps the AI built</description>
    <pubDate>Mon, 05 May 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>thought</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-deploy-blueskylater-to-digitalocean-droplet</guid>
    <title>How to deploy Bluesky Later to DigitalOcean Droplet</title>
    <link>https://www.nico.fyi/blog/how-to-deploy-blueskylater-to-digitalocean-droplet</link>
    <description>
A few days ago I wrote about how to [deploy Bluesky Later on your own server using Coolify](/blog/how-to-deploy-blueskylater-using-coolify). While I recommend it to start using Bluesky Later in your own server, it is possible to run it without Coolify. In this post, I will show you how to run Bluesky Later on a DigitalOcean Droplet using Docker.

First, you need to have a DigitalOcean account. If you don&#39;t have one, you can create one [here](https://m.do.co/c/1a4db88dc63c). Then create a new Droplet using the [Docker image](https://marketplace.digitalocean.com/apps/docker) which you can find in the DigitalOcean Marketplace. For the Droplet size and type, I recommend using the &quot;Basic&quot; type with &quot;Regular (Disk type: SSD)&quot; CPU option, and the lowest price spec ($6/month).

Once the Droplet is created, enter the server using SSH. Then you need to install docker-compose first:

```bash
apt install docker-compose
```

Next, create a `docker-compose.yml` file in the root directory of the ser</description>
    <pubDate>Tue, 18 Feb 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>docker</category><category>bluesky</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/automatically-update-github-project-on-new-release</guid>
    <title>How to automatically update GitHub project on new release</title>
    <link>https://www.nico.fyi/blog/automatically-update-github-project-on-new-release</link>
    <description>
## Back story

In the company I work for, we use [GitHub Projects](https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects) to track our work. It&#39;s very simple and easy to use and has great integration with GitHub repositories and Issues. But it lacks a feature that would be really useful: **automatically update the project when a new release is published**. In our team, we create a new release every time we publish a new version of our software. When a release is created, it automatically creates a new commit in a release branch which triggers a production deployment in Vercel.

When creating a new release, I usually simply set the new tag and use the &quot;Generate release notes&quot; feature to automatically get the release notes from the diff between the previous release and the new one. The generated release notes include the links to pull requests and their authors.

This is great because everyone in the company can see what has been de</description>
    <pubDate>Thu, 13 Feb 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>github</category><category>cli</category><category>open source</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-deploy-blueskylater-using-coolify</guid>
    <title>How to deploy Bluesky Later on your own server using Coolify</title>
    <link>https://www.nico.fyi/blog/how-to-deploy-blueskylater-using-coolify</link>
    <description>
A few months ago I released an open source project called [Bluesky Later](https://blueskylater.com) which is a web app that allows you to schedule Bluesky posts. The app has two modes: Browser mode and Self-hosted mode. In Browser mode, the app runs in the browser and doesn&#39;t need any server to run. In Self-hosted mode, the app runs on a server and you can schedule posts from anywhere.

I have created a docker-compose file so that anyone can easily run Bluesky Later on their own server or computer. In the docker-compose file, all parts of the app are included: a Postgres database, the API server, the frontend, and the cron. So you can run `docker-compose up` and everything will be up and running in a few minutes.

I am using the app on my own server but I don&#39;t run the `docker-compose up` command directly. Instead, I use [Coolify](https://coolify.io) to deploy the app. Coolify is a very cool project and [I&#39;ve been writing about it several times](https://nico.fyi/tags/coolify).

Instal</description>
    <pubDate>Wed, 12 Feb 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>coolify</category><category>bluesky</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/send-email-after-github-release</guid>
    <title>Send E-mail after Creating a New Release in GitHub</title>
    <link>https://www.nico.fyi/blog/send-email-after-github-release</link>
    <description>
In the company I work for, there are some team members who wanted to be notified when a new release is deployed to production. Since we use GitHub, I can easily set up a step in our GitHub Actions workflow to send an e-mail to the team member when a new release is created.

```yml:deploy.yml {20-43} showLineNumbers
name: Deploy to production websites
on:
  workflow_dispatch:
  release:
    types: [created]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Cancel Previous Runs
        uses: styfle/cancel-workflow-action@0.9.1
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Updating release
        run: |
          echo &quot;Release the new version&quot;
          # Do something like push to Vercel
      - name: Send email
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.fastmail.com
          server_port: 465
          username: ${{ secrets.FASTMAIL_EMAIL }}
          password: </description>
    <pubDate>Tue, 28 Jan 2025 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>github</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/be-careful-with-usesyncexternalstore</guid>
    <title>Be careful with useSyncExternalStore</title>
    <link>https://www.nico.fyi/blog/be-careful-with-usesyncexternalstore</link>
    <description>
A few articles ago I wrote about using [useSyncExternalStore to write, read, and react to changes in the local storage](/blog/ssr-friendly-local-storage-react-custom-hook). I even created my own useLocalStorage hook in the same article. But the hook has one flaw. You can only read and write string values because local storage only stores strings.

So I gave it a try to store an object in local storage. My first attempt was like this.

```typescript:app/use-local-storage.ts showLineNumbers
const useLocalStorage = &lt;T,&gt;(key: string, initialValue: T) =&gt; {
  const data = useSyncExternalStore(
    (onChange) =&gt; {
      window.addEventListener(&quot;storage&quot;, onChange);
      return () =&gt; {
        window.removeEventListener(&quot;storage&quot;, onChange);
      };
    },
    () =&gt; {
      const data = localStorage.getItem(key);
      return data ? JSON.parse(data) : initialValue;
    },
    () =&gt; initialValue
  );
};
```

But to my surprise, this code caused an infinite render loop. This happened because </description>
    <pubDate>Sun, 17 Nov 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/generate-subtitles-from-video-using-llm-ai-in-a-single-command</guid>
    <title>How to generate subtitles from video using LLM in a single command</title>
    <link>https://www.nico.fyi/blog/generate-subtitles-from-video-using-llm-ai-in-a-single-command</link>
    <description>
This post is just a quick reminder of how to generate subtitles from video using [subtitles-generator](https://github.com/avivharuzi/subtitles-generator) in a single command.

```shell
npx subtitles-generator -i /path/to/video.mp4 -m base.en
```

It works pretty well. The first time you run it, it would take a while to download the model. But after that, it&#39;s super fast. It&#39;s also super easy to use.

Since I don&#39;t use it often, I put this up in my blog so I can easily find it again.
</description>
    <pubDate>Thu, 14 Nov 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>open-source</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/bluesky-later-schedule-bluesky-post-in-the-future</guid>
    <title>Bluesky later: Schedule Bluesky post in the future for FREE</title>
    <link>https://www.nico.fyi/blog/bluesky-later-schedule-bluesky-post-in-the-future</link>
    <description>
Unless you have been living under a rock for the past few days, you surely have heard about the US election. Due to the result, many people I follow on Twitter have moved to [Bluesky](https://bsky.app) for reasons I&#39;ll let you figure out yourself - it&#39;s not hard to understand.

While I created an account on Bluesky a while back, I haven&#39;t been really active on it. Not only because there weren&#39;t many people there, but it also lacked some features like GIFs! But that&#39;s no longer the case. And since I&#39;ve been thinking of leaving Twitter due to the annoying billionaire, I decided to hang out more on Bluesky and cancelled my Twitter Pro account.

So far it has been a nice experience. These are the features I like the most:

- I can use my domain nico.fyi as my handle! 🤯
- The starter pack created by the community is really helpful to get started. ❤️
- No ads! 🎉
- API is free! 😋

But there&#39;s one thing I&#39;m missing. I want to schedule posts for the future because I don&#39;t want to flood my f</description>
    <pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>bluesky</category><category>open-source</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/exclusive-relationships-in-postgresql</guid>
    <title>Implementing Exclusive Relationships in PostgreSQL and Prisma</title>
    <link>https://www.nico.fyi/blog/exclusive-relationships-in-postgresql</link>
    <description>
Recently we needed to implement a system where a user can do the following by themselves:

- Request for a first-aid card replacement.
- Cancel their course enrollment.
- Reschedule their course enrollment.

And then our admins would like to be able to see the self-service actions that the user has performed with the following information:

- Name
- Course name
- Type of action
- Date
- Paid amount

My co-worker first implemented the model in Prisma something like this:

```prisma:prisma/schema.prisma {11-13} showLineNumbers
enum SelfServiceActionType {
  REQUEST_FIRST_AID_CARD_REPLACEMENT
  CANCEL_COURSE_ENROLLMENT
  RESCHEDULE_COURSE_ENROLLMENT
}

model SelfServiceAction {
  id            Int      @id @default(autoincrement())
  type          SelfServiceActionType
  participantId Int
  certificateId Int?
  courseId      Int?
  newCourseId   Int?
  paidAt        DateTime?
  paidAmount    Float?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
```

While there&#39;s </description>
    <pubDate>Sat, 02 Nov 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>postgres</category><category>prisma</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/llm-instructions-for-v0-and-gpt</guid>
    <title>Instructions for v0 and GPT to generate high quality React code</title>
    <link>https://www.nico.fyi/blog/llm-instructions-for-v0-and-gpt</link>
    <description>
I&#39;ve been using LLMs for the past few months and I can&#39;t imagine going back to the old way of writing code. At the beginning, I tried Copilot but didn&#39;t like it because it actually slowed me down. The generated code frequently missed parentheses, brackets, etc. So I gave it up.

Then I tried [Supermaven](https://supermaven.com). It blew my mind. Not only is it fast in showing code suggestions, but it also generates the code that I need. It&#39;s like it can read my mind. But it actually only reads the files across the project. I feel so productive with it. And what I like the most is that **it generates the code that I want**. It&#39;s like having a jetpack that can boost me to the finish line. It&#39;s different from using ChatGPT where I _ask_ it to create something for me. With Supermaven, I know what I want to write and it will generate it for me.

Then came along [v0](https://v0.dev) by Vercel. It wasn&#39;t great. The generated components weren&#39;t very good. I just didn&#39;t have the knack for writ</description>
    <pubDate>Thu, 31 Oct 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>ai</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/on-the-fly-feature-flag-with-google-sheets</guid>
    <title>On-the-fly feature flag in Next.js with Google Sheets</title>
    <link>https://www.nico.fyi/blog/on-the-fly-feature-flag-with-google-sheets</link>
    <description>
Feature flags are a great way to manage feature toggles in your application. They allow you to enable or disable certain features. There are several common reasons why you might want to use feature flags:

- You want to release a new feature to a subset of your users.
- You want to test a new feature before releasing it to all users.
- You want to disable a feature for a specific user or group of users.

**Feature flags are also useful when you or your team ships to production frequently and you want to make sure that you don&#39;t accidentally deploy a feature that is not ready.** By using feature flags, you can hide unfinished features from your users until they are ready to be released. For example, maybe the UI of your app is ready but the backend is not. You can use feature flags to hide corresponding UI until the backend is ready.

There are several ways you can implement global feature flags that affect the entire application:

- **You can set the flags during build time**: You sto</description>
    <pubDate>Wed, 16 Oct 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/long-running-jobs-nextjs-redis-bull</guid>
    <title>Running Long Jobs with Queues in Next.js using Bull and Redis</title>
    <link>https://www.nico.fyi/blog/long-running-jobs-nextjs-redis-bull</link>
    <description>
&lt;div className=&quot;rounded-md border bg-green-100 px-4 py-2 dark:bg-slate-900&quot;&gt;
  ℹ️ This is a guest post by my awesome fellow dev in
  [Hyperjump](https://hyperjump.tech/en?utm_source=nico.fyi&amp;utm_medium=blog) Technology, [Denny
  Pradipta](https://dennypradiptablog.vercel.app).
&lt;/div&gt;

When building web apps with Next.js, handling long-running tasks like data processing or sending emails can be tricky. You don’t want to keep your users waiting while the server completes the job, right? This is where background processing comes in handy.

In this article, I will share how we handle long processes in [our uptime monitoring tool called Neo Sense](https://console.neosense.id?utm_source=nico.fyi&amp;utm_medium=referral&amp;utm_campaign=blogpost) using [Bull](https://github.com/OptimalBits/bull)—a powerful queue system built on top of Redis. Bull makes it easy to offload tasks to background workers, keeping your app fast and responsive. Plus, Redis helps manage the queue efficiently.

## Prerequisit</description>
    <pubDate>Fri, 11 Oct 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/tailwind-css-group-modifier-to-prevent-react-rerender</guid>
    <title>How to prevent re-render in React with Tailwind CSS</title>
    <link>https://www.nico.fyi/blog/tailwind-css-group-modifier-to-prevent-react-rerender</link>
    <description>
Recently I was tasked with updating a minor change in [Hyperjump Technology&#39;s website](https://hyperjump.tech/en?utm_source=nico.fyi&amp;utm_medium=blog). But I thought it was a good chance to completely rewrite the website from scratch using Next.js because previously it was just a bunch of HTML and CSS files (I was lazy 😂). The good thing was we already used Tailwind CSS for the styling. So I simply just needed to copy the HTML to the new Next.js-powered website.

In the old website, there is a sticky navigation bar on the top of the page which has transparent background when the scoll position is at the top. But when user scrolls down, the background becomes white. It was implemented as follows:

```html:index.html showLineNumbers
&lt;nav id=&quot;header&quot; class=&quot;fixed w-full z-30 top-0 text-white&quot;&gt;
  &lt;!-- content of the navigation --&gt;
&lt;/nav&gt;
```

```javascript:main.js {7,10} showLineNumbers
var header = document.getElementById(&quot;header&quot;);
document.addEventListener(&quot;scroll&quot;, function () {
  /*A</description>
    <pubDate>Thu, 10 Oct 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>react</category><category>tailwind</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/next-js-code-config-based-routing</guid>
    <title>Code based routing in Next.js</title>
    <link>https://www.nico.fyi/blog/next-js-code-config-based-routing</link>
    <description>
Some people seem to hate the file-based routing in Next.js. They prefer to use code-based routing instead. And with [Remix, a.k.a, React Router 7](https://x.com/remix_run/status/1841926043080523813) releasing a new config based routing, many people seem to be excited about it.

But many don&#39;t seem to know that it has been possible to do code-based routing in Next.js. You just have to use the [optional catch-all segments of the dynamic routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#optional-catch-all-segments) and library like [path-to-regexp](https://github.com/pillarjs/path-to-regexp).

First, create a file in `app/[[...paths]]/page.tsx`:

```typescript:app/[[...paths]]/page.tsx {6-9, 26-36} showLineNumbers
import UserIdPage from &quot;@/page-components/user-id&quot;;
import UsersPage from &quot;@/page-components/users&quot;;
import { match } from &quot;path-to-regexp&quot;;

// define your routes here
const routes = [
  { matcher: match(&quot;/users/:userId&quot;), component: UserIdPa</description>
    <pubDate>Sun, 06 Oct 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/ssr-friendly-local-storage-react-custom-hook</guid>
    <title>SSR-friendly Custom React Hook for Local Storage Read and Write</title>
    <link>https://www.nico.fyi/blog/ssr-friendly-local-storage-react-custom-hook</link>
    <description>
import { Alert } from &#39;@/components/ui/alert&#39;

&lt;Alert className=&quot;mt-8&quot;&gt;
  Update: Use my [useLocalStorage hook](https://github.com/nicnocquee/use-local-storage) for
  convenience.
&lt;/Alert&gt;

There are hundreds of [custom React hooks to access the local storage](https://www.npmjs.com/search?q=use-local-storage) out there. But I was tempted to create my own using [useSyncExternalStore](/blog/use-sync-external-store-in-react) since I figured it&#39;s the proper hooks for this kind of use case. Now I&#39;m sure there are already a package that does exactly what I want, but I wanted to create it by myself, for the sake of learning.

This was my first attempt.

```jsx:app/use-local-storage.ts showLineNumbers
const useLocalStorage = (
  key: string,
  initialValue: string
) =&gt; {
  const data = useSyncExternalStore(
    (onChange) =&gt; {
      window.addEventListener(&quot;storage&quot;, onChange);
      return () =&gt; {
        window.removeEventListener(&quot;storage&quot;, onChange);
      };
    },
    () =&gt; {
      cons</description>
    <pubDate>Sun, 29 Sep 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-manipulate-search-params-in-next-js</guid>
    <title>How to manipulate search params in Next.js easily</title>
    <link>https://www.nico.fyi/blog/how-to-manipulate-search-params-in-next-js</link>
    <description>
In Next.js App Router, you can get the current URL&#39;s query string by using [`useSearchParams` hook](https://nextjs.org/docs/app/api-reference/functions/use-search-params). When called, **it returns read-only version of the URLSearchParams**. That means, if you to set a new value to the search params, you need to create a new search params string by merging the current search params with the new value like this:

```jsx {2,5,10-11} showLineNumbers
&#39;use client&#39;;
import { useSearchParams, useRouter } from &#39;next/navigation&#39;

const Component = () =&gt; {
  const searchParams = useSearchParams();
  const router = useRouter();

  const createQueryString = useCallback(
    (name: string, value: string) =&gt; {
      const params = new URLSearchParams(searchParams.toString())
      params.set(name, value)

      return params.toString()
    },
    [searchParams]
  )

  return (
    &lt;button onClick={() =&gt; {
        router.push(pathname + &#39;?&#39; + createQueryString(&#39;sort&#39;, &#39;asc&#39;))
      }}&gt;
        ASC
 </description>
    <pubDate>Sat, 28 Sep 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/reset-state-from-react-useactionstate</guid>
    <title>How to reset the state of useActionState in React</title>
    <link>https://www.nico.fyi/blog/reset-state-from-react-useactionstate</link>
    <description>
I wrote before about how easy it is to create [infinite scroll with server action and useActionState in Next.js](/blog/infinite-scroll-with-server-action-and-use-action-state). `useActionState` is that hook that I&#39;m so glad that it&#39;s created to replace the confusing and less capable `useFormState` hook.

But there&#39;s one thing that is still bugging me. The `useActionState` hook doesn&#39;t have a way to reset the state. It&#39;s kinda weird that the state cannot be reseted easily. Imagine I have a form that allows user to submit something. When the submission completes successfully, they will see some message from the server. But then I want them to be able to reset the form and fill the fields with another information. When they reset the form, the message from the server should not be shown anymore. For example,

```typescript:app/products/page.tsx showLineNumbers
&quot;use client&quot;;
import { useRef, useActionState } from &quot;react&quot;;
import { doSomething } from &quot;./actions&quot;;

export default function F</description>
    <pubDate>Tue, 24 Sep 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/protect-nextjs-page-with-authorization-check</guid>
    <title>How to protect Next.js App Router&#39;s page with authorization check</title>
    <link>https://www.nico.fyi/blog/protect-nextjs-page-with-authorization-check</link>
    <description>
If you haven&#39;t read it, Next.js has updated their docs [Authentication and Authorization](https://nextjs.org/docs/app/building-your-application/authentication). One of the most important gems in the docs, in my opinion, is the [Authorization Check](https://nextjs.org/docs/app/building-your-application/authentication#authorization-check) section. Since there are a lot of parts that are involved when a request is made to a server such as middleware, layout, page, etc, it&#39;s great to finally have an official guide on how to perform authorization checks.

In this post I&#39;m going to share a practical way to check authorization when a user visits a page. But before that, you should know the three important take aways from the docs:

- Perform _only_ optimistic authorization checks in the middleware. It&#39;s a fancy way of saying that you should do checks that doesn&#39;t require any database or other external APIs because it can cause performance issues since middleware runs on every request by defa</description>
    <pubDate>Mon, 23 Sep 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react</category><category>next.js</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/infinite-scroll-with-server-action-and-use-action-state</guid>
    <title>How to create infinite scroll with server action and useActionState in Next.js</title>
    <link>https://www.nico.fyi/blog/infinite-scroll-with-server-action-and-use-action-state</link>
    <description>
Let&#39;s imagine we want to show a list of posts. At the bottom of the list, we want to show a button that allows the user to load more posts. Before [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) and [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations), we would have to

- store the posts in a state using `useState`
- fetch the posts from an API endpoint using `fetch`
- store the loading state in a state using `useState`
- create the API end point

But Server Actions and [`useActionState`](https://react.dev/reference/react/useActionState) simplify all of this. First, let&#39;s create a page component that [starts the work of getting the posts from the database and stream the promise to the client](http://localhost:3000/blog/simplify-data-fetching-with-rsc-suspense-and-use-api-in-next-js) as shown in the following code:

```typescript:app/infinite-scroll/page.tsx {6,11}</description>
    <pubDate>Sun, 22 Sep 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react</category><category>next.js</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-deploy-single-file-website-to-vercel</guid>
    <title>How to deploy a single file website to Vercel</title>
    <link>https://www.nico.fyi/blog/how-to-deploy-single-file-website-to-vercel</link>
    <description>
Vercel is well known for deploying Next.js apps. But it is less known that it also supports [deploying projects from other frameworks](https://vercel.com/docs/frameworks/more-frameworks) like Angular, Solid, Svelte, and many more. Something even more obscure is that you can deploy a single file website to Vercel.

The secret is to use Vercel&#39;s Serverless functions. All you have to do is create a file in a directory called `api`, e.g., `api/hello.js`. In this file, you need to export a `GET` function that will be called by Vercel when the user visits the `/api/hello` endpoint.

```typescript:api/hello.js
export function GET(request) {
  return new Response(`Hello from ${process.env.VERCEL_REGION}`);
}
```

## Serving HTML

To show a website in this end point, we need to return a HTML response instead of text response as shown in the example above.

```typescript:api/hello.js
export async function GET() {
  const html = `
    &lt;!DOCTYPE html&gt;
    &lt;html lang=&quot;en&quot;&gt;
    &lt;head&gt;
        &lt;meta</description>
    <pubDate>Sun, 15 Sep 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>vercel</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/react-usetransition-in-next-js</guid>
    <title>Interesting behaviour of useTransition in Next.js</title>
    <link>https://www.nico.fyi/blog/react-usetransition-in-next-js</link>
    <description>
One of the very common user interaction in a web application is calling an API end point where data mutation is performed when a button is clicked. Since it performs a mutation, **usually we don&#39;t want the user to be able to click the button again until the mutation is completed**. Otherwise there will be inconsistency in the user&#39;s data. For example, user needs to click a button to spend some points. But if the button is clicked again before the points are spent, the points will be deducted again.

The common way to solve this little problem in React is to use `useState` to store the loading state of the API call. Then we can use the `disabled` prop to disable the button until the loading state is `false`.

```typescript:do-something.tsx {3,6,10,13,22} showLineNumbers
&quot;use client&quot;;

import { useState } from &quot;react&quot;;

export default function DoSomething() {
  const [isPending, setIsPending] = useState(false);
  return (
    &lt;div&gt;
      &lt;button
        disabled={isPending}
        clas</description>
    <pubDate>Tue, 10 Sep 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/next-js-sequential-tasks-progress-stepper-with-rsc-and-suspense</guid>
    <title>How to Show Task Sequence Progress with React Suspense and RSC in Next.js</title>
    <link>https://www.nico.fyi/blog/next-js-sequential-tasks-progress-stepper-with-rsc-and-suspense</link>
    <description>
Imagine you allow a user to make a purchase of a service or product. If you use one of the popular payment gateways, you can easily show the payment page provided by the payment gateway. Once the user completes the payment, the payment gateway usually does two things:

1. Redirects the user to a confirmation or success page that you own.
2. At the same time, it notifies your server that the payment is completed via a webhook.

Now say you want to show the steps that are performed on the confirmation or success page:

1. Confirm that the payment is completed successfully. Sometimes your server might not have received the webhook from the provider yet by the time the user reaches the confirmation page. So the user needs to stay on this first step until the webhook is received and the payment is confirmed.
2. Once the payment is confirmed, let&#39;s imagine that you need to call another third-party API to create a resource. For example, you might use a third-party API to create a personalize</description>
    <pubDate>Wed, 04 Sep 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/prisma-typed-sql</guid>
    <title>The new TypedSQL in Prisma: Raw query with type safety</title>
    <link>https://www.nico.fyi/blog/prisma-typed-sql</link>
    <description>
Prisma has just announced a new feature in v5.19.0: [TypedSQL](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/typedsql). With this new feature, you can write SQL queries as usual and get a type-safe result. And as this is from Prisma, they designed it to be statically generated like the prisma client functions.

## Usage

First you need to upgrade your Prisma version to `5.19.0` or higher. Then you need to enable the preview feature in your schema:

```prisma:schema.prisma
generator client {
  provider        = &quot;prisma-client-js&quot;
  previewFeatures = [&quot;typedSql&quot;]
}
```

Next, you just need to write your SQL in a file inside the `prisma/sql` directory. For example, say I have the following schema:

```prisma:schema.prisma
model Question {
  id                       String                    @id @default(uuid())
  code                     String
  question_sets            String[]
  names                    Json
  status                   Int                       @default(0)</description>
    <pubDate>Wed, 28 Aug 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>prisma</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-deploy-multiple-versions-react-native-ios-to-testflight-from-azure-devops-pipeline</guid>
    <title>How to deploy App Store and Beta versions of React Native iOS app from Azure DevOps pipeline</title>
    <link>https://www.nico.fyi/blog/how-to-deploy-multiple-versions-react-native-ios-to-testflight-from-azure-devops-pipeline</link>
    <description>
A few weeks ago, I wrote a [guide on how to deploy React Native iOS app to TestFlight from Azure DevOps pipeline](/blog/how-to-deploy-react-native-ios-to-testflight-from-azure-devops-pipeline). Once you and testers in your team have made sure that the app is working by installing it via TestFlight, the owner of the app can submit the app to the App Store. The problem is the testers cannot install both versions of the app (TestFlight and App Store versions) in the same iPhone. So they either need to have two iPhones or to waste time by installing and reinstalling the app every time they want to test it.

In this guide, I&#39;ll show you how to deploy two versions of the app to TestFlight. This way the testers can install the beta version of the app from TestFlight and the app store version from the App Store in the same iPhone. I highly recommend you to read the previous article first.

## Tool Versions

Before we start, let me tell you the versions of the tools I&#39;m using as of this writin</description>
    <pubDate>Tue, 27 Aug 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react native</category><category>ios</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/add-comment-on-table-and-column-in-postgres</guid>
    <title>How to add comment on table and column in Postgres</title>
    <link>https://www.nico.fyi/blog/add-comment-on-table-and-column-in-postgres</link>
    <description>
I was just wondering if it&#39;s possible to add a comment on a table and column in Postgres. After a quick talk with AI, I found out that it&#39;s possible. Here&#39;s how you can do it:

```sql
COMMENT ON TABLE &quot;public&quot;.&quot;table_name&quot; IS &#39;This is a comment&#39;;
COMMENT ON COLUMN &quot;public&quot;.&quot;table_name&quot;.&quot;column_name&quot; IS &#39;This is a comment&#39;;
```

When you look at the table and column using a GUI client like TablePlus, you will see the comments:

![](/static/images/articles/postgres-comment.png.webp)

I think it&#39;s a pretty useful feature to have since there are times when a developer uses a non-descriptive name for a table or column. I was kind of hoping that Prisma supports this, where comments in the schema file are automatically added to the migration files. The only workaround I can think of is to run Prisma&#39;s migration without executing it first and then add the comments manually.

For example, say you just created a schema file or you make some changes to the schema file. First, create a draft migr</description>
    <pubDate>Sat, 17 Aug 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>postgres</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/quickly-generate-postgres-database-in-a-single-command</guid>
    <title>Quickly generate Postgres database in a single command</title>
    <link>https://www.nico.fyi/blog/quickly-generate-postgres-database-in-a-single-command</link>
    <description>
I was curious if it&#39;s possible to create a small app entirely by AI. After a day or so, with the help of Claude Sonnet 3.5 and GPT-4 via Raycast, I finally created a Node.js CLI tool called [create-postgresql](https://github.com/nicnocquee/create-postgresql) that can quickly generate a Postgres database in a single command. You can use it for FREE and without any registration, but the server deletes all the generated databases every day at midnight UTC.

![](/static/images/articles/create-postgresql-raycast.png.webp)

The whole app actually consists of several parts:

- A Node.js CLI tool that connects to the server and creates the database.
- An Express server that receives the request from the CLI tool and creates the database.
- A static web page to verify the request before creating the database.
- A PostgreSQL instance that is used to store the databases.
- A PgBouncer instance that is used to pool the PostgreSQL instance.

I wanted all the server parts including the PostgreSQL a</description>
    <pubDate>Thu, 15 Aug 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>ubuntu</category><category>cli</category><category>coolify</category><category>postgres</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-deploy-react-native-ios-to-testflight-from-azure-devops-pipeline</guid>
    <title>How to deploy React Native iOS app to TestFlight from Azure DevOps pipeline</title>
    <link>https://www.nico.fyi/blog/how-to-deploy-react-native-ios-to-testflight-from-azure-devops-pipeline</link>
    <description>
When you develop an iOS app, Android app, or a cross-platform app using React Native in a company, there is one thing you need to implement as soon as possible to save you tons of troubles in the future: **the CI/CD (Continuous Integration/Continuous Deployment) pipeline to deploy or submit the app to App Store or Play Store**. Because unlike deploying websites, deploying mobile apps has a lot of steps. The sooner you can automate the deployment process, the faster you can get the app out to users, and in return, you can get feedback faster too. Even if only to internal users.

Aside from being able to have a quick feedback loop, the main purpose of the CI/CD pipeline to deploy or submit the app to App Store or Play Store is **to prevent only a single person from being able to deploy the app**. We don&#39;t want to be unable to deploy the app if the person is not available or having problems with their computer.

In the Apple ecosystem, we can deploy beta versions of our apps to TestFligh</description>
    <pubDate>Sat, 10 Aug 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react native</category><category>ios</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/the-subtle-difference-between-capture-closure-and-pass-arg-func</guid>
    <title>Closure vs Function in JavaScript</title>
    <link>https://www.nico.fyi/blog/the-subtle-difference-between-capture-closure-and-pass-arg-func</link>
    <description>
In [the last blog post](/blog/memory-issue-in-javascript-and-closures) I discussed about a subtle memory issue in JavaScript and how I found out that `setTimeout` can accept more than two arguments. It&#39;s reassuring that, based on some Twitter comments, I wasn&#39;t the only one to discover this.

But then [Ryan Dsouza on Twitter asked why the fix in the post worked](https://x.com/ryands1701/status/1821087444445307003). I thought I understood the solution but it turned out to be a bit more complicated than I thought. After spending a few hours pondering, I think I can explain it

# The original problem

So let&#39;s take a look at the original code:

```javascript:memory-leak-js.html {2-7} showLineNumbers
function demo() {
  const bigArrayBuffer = new ArrayBuffer(100_000_000)
  const id = setTimeout(() =&gt; {
    console.log(bigArrayBuffer.byteLength)
  }, 1000)

  return () =&gt; clearTimeout(id)
}

let cancelDemo

document.getElementById(&#39;runDemo&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  cancelDemo =</description>
    <pubDate>Wed, 07 Aug 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>javascript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/memory-issue-in-javascript-and-closures</guid>
    <title>Subtle memory issues in JavaScript thanks to closures</title>
    <link>https://www.nico.fyi/blog/memory-issue-in-javascript-and-closures</link>
    <description>
Recently, I came across an interesting blog post by Jake Archibald titled [Garbage collection and closures](https://jakearchibald.com/2024/garbage-collection-and-closures) where he shows how mishandling closures can lead to memory leaks. The `demo` function in the following snippet is the problematic code:

```html:memory-leak.html {12-19, 30} showLineNumbers
&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Memory Leak Test&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;button id=&quot;runDemo&quot;&gt;Run Demo&lt;/button&gt;
    &lt;button id=&quot;cancelDemo&quot;&gt;Cancel Demo&lt;/button&gt;
    &lt;button id=&quot;forceGC&quot;&gt;Force Garbage Collection&lt;/button&gt;

    &lt;script&gt;
      function demo() {
        const bigArrayBuffer = new ArrayBuffer(100_000_000)
        const id = setTimeout(() =&gt; {
          console.log(bigArrayBuffer.byteLength)
        }, 1000)

        return () =&gt; clearTimeout(id)
      }

      let cancelDemo

      document.getElementById(&#39;runDemo&#39;).addEventListener(&#39;click&#39;, () =&gt; {
        cancelDemo = demo()
      })

      document</description>
    <pubDate>Sun, 04 Aug 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>javascript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/dockerfile-for-faker-postgres-to-generate-fake-realistic-data</guid>
    <title>How to generate fake data in Postgres with faker_fdw</title>
    <link>https://www.nico.fyi/blog/dockerfile-for-faker-postgres-to-generate-fake-realistic-data</link>
    <description>
Few months ago I wrote about [how to populate a Postgres database quickly using `generate_series` function](/blog/populate-postgres-fake-data). It is a function that generates a set of values, from start to stop, at the interval specified by step. But in my example, the samples have values selected from a limited set of options.

In this post, I&#39;ll show you a better way to generate fake data in Postgres using [faker_fdw](https://github.com/guedes/faker_fdw). You can follow the installation steps from the repository of faker_fdw to install faker_fdw. But if you want to quickly give it a try, you can simply use the following Dockerfile to create a Docker image that has Postgres and faker_fdw installed:

```dockerfile:Dockerfile showLineNumbers
# Stage 1: Build environment
FROM postgres:16 AS builder

# Switch to root to install dependencies
USER root

# Install necessary build dependencies
RUN apt-get update &amp;&amp; apt-get install -y \
    build-essential \
    wget \
    postgresql-server-</description>
    <pubDate>Mon, 29 Jul 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>docker</category><category>postgres</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/dockerfile-for-pgcron-postgres-cron-job</guid>
    <title>How to run pg_cron via Dockerfile</title>
    <link>https://www.nico.fyi/blog/dockerfile-for-pgcron-postgres-cron-job</link>
    <description>
Few months ago I wrote about [how you can have cron jobs in Postgres using pg_cron](/blog/cron-job-with-postgres). But I didn&#39;t really specify how to install pg_cron. While pg_cron is a great addition to Postgres, it&#39;s not a built-in feature.

So in this post, I&#39;ll show you how to quickly have a Postgres that has pg_cron installed. You can simply use the following Dockerfile to install pg_cron:

```dockerfile:Dockerfile showLineNumbers
FROM postgres:15
RUN apt-get update &amp;&amp; apt-get install -y curl
RUN apt-get -y install postgresql-15-cron
RUN echo &quot;shared_preload_libraries=&#39;pg_cron&#39;&quot; &gt;&gt; /usr/share/postgresql/postgresql.conf.sample
RUN echo &quot;cron.database_name=&#39;postgres&#39;&quot; &gt;&gt; /usr/share/postgresql/postgresql.conf.sample
```

Then you need to build the image first:

```shell
docker build -t postgres-with-cron .
```

Once the image is built, you can run it with the following command:

```shell
docker run  \
  --name postgres-cron \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 </description>
    <pubDate>Tue, 23 Jul 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>docker</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-install-run-pgboss-via-coolify</guid>
    <title>How to install and run pg-boss via Coolify</title>
    <link>https://www.nico.fyi/blog/how-to-install-run-pgboss-via-coolify</link>
    <description>
In real world applications, having a way to run tasks, operations, or jobs in the background is inevitable. Even in a common and seemingly simple feature like registration, there are many scenarios where you need to run a task in the background. For example, you might need to send an email to a user after they register. Or you might need to update a user&#39;s profile after they register. Or you might need to send a notification to a user after they upload a file. In these cases, you need to have a way to run these tasks in the background because they might take a long time to complete.

In Hyperjump&#39;s flagship app, [Neo Sense](https://console.neosense.id), we use [Bull](https://github.com/OptimalBits/bull) to run background tasks which uses Redis under the hood. Bull provides an API that takes care of all the low-level details and enriches Redis basic functionality so that more complex use-cases can be handled easily. Bull uses Redis because it is great for queue system.

While Bull is g</description>
    <pubDate>Thu, 18 Jul 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>ubuntu</category><category>coolify</category><category>postgres</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-install-run-pgbouncer-via-coolify</guid>
    <title>How to install and run PgBouncer via Coolify</title>
    <link>https://www.nico.fyi/blog/how-to-install-run-pgbouncer-via-coolify</link>
    <description>
A month ago I wrote about [how to deploy a Next.js app that connects to Postgres with Prisma via Coolify](/blog/how-to-deploy-next-js-prisma-postgres-using-coolify). It turned out to be a popular post according to the number of visits from Google Search. So this time I&#39;m sharing how to install an important tool if you are using PostgreSQL: [PgBouncer](https://www.pgbouncer.org).

PgBouncer is a connection pooler for PostgreSQL. **In real world applications, it is almost always required to have a connection pooler like PgBouncer** because [new connections are expensive](https://stackoverflow.blog/2020/10/14/improve-database-performance-with-connection-pooling/) in PostgreSQL. Many of you might have seen error message saying that there no more connections available. The maximum number of connections usually depends on the spec of your server. For example, in managed PostgreSQL service in Digital Ocean, the maximum number of connections for 1GB RAM 1vCPU is 25 connections but 3 are alrea</description>
    <pubDate>Wed, 17 Jul 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>ubuntu</category><category>cli</category><category>coolify</category><category>postgres</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/what-every-developer-needs-to-do</guid>
    <title>What every developer needs to do</title>
    <link>https://www.nico.fyi/blog/what-every-developer-needs-to-do</link>
    <description>
One thing I wish I had done when I was younger is write more. Specifically, I regret not documenting the lessons I learned throughout my career. I should have written about the mistakes I made in software development and the solutions I found to overcome those challenges.

That&#39;s why my 2024 resolution was to write more. Initially, I planned to write five articles per week, but I have only managed to write two or three lately. Coming up with fresh ideas every week is challenging, but it seems to be paying off. The Google search performance of my blog has been steadily increasing.

![](/static/images/articles/google-search-performance-2024-01.png.webp)

This blog also achieved 1K Google Search Impact recently!

![](/static/images/articles/google-search-impact-1k.png.webp)

This may seem like a small achievement for some people, but we should celebrate small victories, right?

## The Importance of Writing

What I&#39;m emphasizing in this post is that as a developer, especially when you&#39;re </description>
    <pubDate>Wed, 10 Jul 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/demistifying-cache-in-nextjs</guid>
    <title>Demistifying cache in Next.js</title>
    <link>https://www.nico.fyi/blog/demistifying-cache-in-nextjs</link>
    <description>
If you&#39;ve been following my blog for a while, you might notice that I&#39;m a big fan of React Server Components (RSC) in Next.js. For example, I&#39;ve written about the new way of fetching data in the era of RSC and Suspense in [this post](/blog/simplify-data-fetching-with-rsc-suspense-and-use-api-in-next-js).

In this blog post, I&#39;m going to share one of the important tools when developing an RSC-powered app: **the cache**. You need to understand about the caching mechanism because RSC needs it to be performant as I will show later.

In Next.js, you can use two different caching mechanisms: the default `cache` and the `unstable_cache`. The [`cache` function](https://react.dev/reference/react/cache) is provided by React while the [`unstable_cache` function](https://nextjs.org/docs/app/api-reference/functions/unstable_cache) is provided by Next.js.

## cache

Let&#39;s say you have a function that checks if a user is logged in.

```typescript:session.ts showLineNumbers
import &#39;server-only&#39;;

exp</description>
    <pubDate>Sun, 07 Jul 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/pull-request-best-practices-names-nitpicking-is-ok</guid>
    <title>Pull Request Best Practices: Name Nitpicking is OK</title>
    <link>https://www.nico.fyi/blog/pull-request-best-practices-names-nitpicking-is-ok</link>
    <description>
Some developers sometimes complain when their colleagues hold back their pull requests because they request some variable name changes. They think it&#39;s nitpicking.

![](/static/images/articles/nitpicking.png.webp)

I&#39;m going to explain why it&#39;s not always nitpicking. Sometimes it&#39;s needed.

## 🙅 Misleading Names

When a variable name is misleading, it can justify a request for change. For example, a variable named `durationInSeconds` that actually holds an `object` instead of an `integer` is misleading. Such names can lead to incorrect assumptions about the type and use of the variable, potentially causing errors and confusion.

## 😵‍💫 Confusing Names

Names that cause logical confusion are also a valid concern. For instance, a variable like `isNotActive` used in a statement `if (!isNotActive)` creates a double negative that&#39;s hard to follow. In such cases, renaming to `isActive` simplifies understanding, making the code more readable and less prone to errors.

## 😵 Non-descriptiv</description>
    <pubDate>Tue, 02 Jul 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>pull request</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/the-unintuitive-next-js-defaults</guid>
    <title>The unintuitive default behaviors in Next.js 14 App Router</title>
    <link>https://www.nico.fyi/blog/the-unintuitive-next-js-defaults</link>
    <description>
While I personally enjoy using the Next.js App Router, many don&#39;t share my enthusiasm. Most of the time, they question the purpose of having React Server Components (RSC) in the Next.js App Router because they encounter some quirks. I share their frustration because it has happened to me too.

I belive that this frustration stems from the fact that **there are some default behaviors in the Next.js App Router that are unintuitive**.

# Static Rendering as the default for RSC

This behaviour bit me multiple times. Take a look at this simple example:

```typescript:app/page.tsx
export default async function Page() {
  return (
    &lt;div&gt;
      &lt;p&gt;{new Date().toISOString()}&lt;/p&gt;
    &lt;/div&gt;
  )
}
```

In production, this page will show you a date and time, but not the current date and time! This wasted me several hours when I started using App Router. **This happened because the page is statically rendered, which means the displayed date and time reflect when the page was built.** I find thi</description>
    <pubDate>Mon, 01 Jul 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/create-sftp-only-user-in-ubuntu-server</guid>
    <title>How to create SFTP-only user in Ubuntu Server</title>
    <link>https://www.nico.fyi/blog/create-sftp-only-user-in-ubuntu-server</link>
    <description>
When I was playing with Coolify, for no particular reason I wanted to create a user in my Ubuntu server that only had access to SFTP. I found some tutorials online but I thought it would be a good idea to write a script to do this.

```shell:create-sftp-user.sh
#!/bin/bash

if [ $# -eq 0 ]; then
  echo &quot;Please provide a username as an argument.&quot;
  exit 1
fi

username=$1

sudo adduser --shell /bin/false --disabled-password --gecos &quot;&quot; $username

echo &quot;Enter the password for the user &#39;$username&#39;:&quot;
read -s password
echo

echo &quot;$username:$password&quot; | sudo chpasswd

sftp_directory=&quot;/var/sftp&quot;
user_directory=&quot;$sftp_directory/$username&quot;
sudo mkdir -p &quot;$user_directory&quot;
sudo chown root:root &quot;$sftp_directory&quot;
sudo chown $username:$username &quot;$user_directory&quot;
sudo chmod 755 &quot;$sftp_directory&quot;
sudo chmod 700 &quot;$user_directory&quot;

sudo tee -a /etc/ssh/sshd_config &gt; /dev/null &lt;&lt;EOL
Match User $username
	ForceCommand internal-sftp
	PasswordAuthentication yes
	ChrootDirectory $sftp_directory
	PermitTunnel </description>
    <pubDate>Mon, 24 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>ubuntu</category><category>cli</category><category>coolify</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/why-ignoring-sunk-cost-leads-to-better-decision-making</guid>
    <title>Why Ignoring Sunk Costs Leads to Better Decision Making</title>
    <link>https://www.nico.fyi/blog/why-ignoring-sunk-cost-leads-to-better-decision-making</link>
    <description>
Have you ever felt stuck in a project because you&#39;ve already put in so much time and money? Or kept reading a boring book just because you&#39;re already halfway through? If yes, you’ve experienced **the sunk cost fallacy**.

## What is the Sunk Cost Fallacy?

The sunk cost fallacy is when you continue investing in something because of the money, time, or effort you&#39;ve already put in, even if it&#39;s not the best choice anymore. These past investments are called &quot;sunk costs&quot; because they can&#39;t be recovered.

Warren Buffett once said,

&gt; The most important thing to do if you find yourself in a hole is to stop digging.

It means that just because you&#39;ve spent a lot on something, it doesn&#39;t mean you should keep spending more if it&#39;s not worth it.

## An Everyday Example

Imagine you’ve spent \$100 on a concert ticket. On the day of the concert, you feel unwell and don’t want to go. If you think, “I’ve already spent \$100, I have to go,” that&#39;s the sunk cost fallacy. The money is already spent a</description>
    <pubDate>Thu, 20 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>philosophy</category><category>personal</category><category>thought</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/deploy-next-js-prisma-postgres-using-coolify</guid>
    <title>How to deploy a Next.js app with Prisma and Postgres using Coolify</title>
    <link>https://www.nico.fyi/blog/deploy-next-js-prisma-postgres-using-coolify</link>
    <description>
Ever since [the drama around Vercel pricing](/blog/should-you-use-vercel), [Coolify](https://coolify.io/) has become a popular choice for self-hosting. Coolify makes it easy to deploy web apps on your own server with just a few clicks. It was created to challenge hosting platforms like Vercel, which are famous for their ease of use but tend to be pricey. However, as I suspected, self-hosting is not as easy as some people make it out to be.

Deploying a static Next.js app via Coolify is indeed straightforward. I moved my [Pull Request Best Practices](https://pr.nico.fyi) book website from Vercel to my own server using Coolify. You just need to add the repository URL of your project to the Coolify dashboard. Then, you can click the &quot;Deploy&quot; button. Coolify will automatically build your app and deploy it to your server.

But what if you want to deploy a Next.js app with Prisma and Postgres? It turns out it&#39;s not that easy. I encountered several annoying problems, and I&#39;m going to share m</description>
    <pubDate>Wed, 19 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>ubuntu</category><category>cli</category><category>coolify</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/simplify-data-fetching-with-rsc-suspense-and-use-api-in-next-js</guid>
    <title>Simplify data fetching with RSC, Suspense, and use API in Next.js</title>
    <link>https://www.nico.fyi/blog/simplify-data-fetching-with-rsc-suspense-and-use-api-in-next-js</link>
    <description>
Data fetching in the React ecosystem has been a hot topic for a long time. Since React is not opinionated about how data is fetched, the community has come up with various solutions.

## Fetch-in-effect

One solution that is simple and doesn&#39;t need any dependencies is using JavaScript&#39;s `fetch` and the `useEffect` hook.

```typescript
function ProfilePage() {
  const [user, setUser] = useState(null);

  useEffect(() =&gt; {
    fetchUser().then(u =&gt; setUser(u));
  }, []);

  if (user === null) {
    return &lt;p&gt;Loading profile...&lt;/p&gt;;
  }
  return (
    &lt;&gt;
      &lt;h1&gt;{user.name}&lt;/h1&gt;
      &lt;ProfileTimeline /&gt;
    &lt;/&gt;
  );
}

function ProfileTimeline() {
  const [posts, setPosts] = useState(null);

  useEffect(() =&gt; {
    fetchPosts().then(p =&gt; setPosts(p));
  }, []);

  if (posts === null) {
    return &lt;h2&gt;Loading posts...&lt;/h2&gt;;
  }
  return (
    &lt;ul&gt;
      {posts.map(post =&gt; (
        &lt;li key={post.id}&gt;{post.text}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}
```

However, this approach has some drawba</description>
    <pubDate>Tue, 18 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-fix-invalid-login-in-minio-deployed-via-coolify</guid>
    <title>How to fix invalid login in Minio deployed via Coolify</title>
    <link>https://www.nico.fyi/blog/how-to-fix-invalid-login-in-minio-deployed-via-coolify</link>
    <description>
![](/static/images/articles/coolify.png.webp)

Playing with [Coolify](https://coolify.io) has been **both nice and a bit frustrating**. For some applications and services, it&#39;s so easy to install, deploy, and use. But for others, it&#39;s a real pain in the ass.

One of the apps I&#39;m trying to deploy with Coolify is [Minio](https://minio.io/), an S3-compatible object storage server. While I could deploy it successfully, I never managed to log in to the dashboard. I searched the internet for hours, but I couldn&#39;t find a solution. Some people had the same problem and mentioned that they could fix it by changing the proxy in Coolify to [Caddy](https://caddyserver.com) instead of [Traefik](https://traefik.io/traefik/). But I&#39;ve been using Caddy this whole time, and it didn&#39;t work.

For experimentation, I spun up a new server in [Hetzner](https://www.hetzner.com) and added it to Coolify. I then deployed Minio without changing anything in the server or Minio configuration. I was able to log in t</description>
    <pubDate>Mon, 17 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>ubuntu</category><category>cli</category><category>coolify</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/blocking-external-access-to-postgres-in-docker-with-ufw-in-ubuntu</guid>
    <title>Blocking External Access to PostgreSQL in Docker with UFW on Ubuntu</title>
    <link>https://www.nico.fyi/blog/blocking-external-access-to-postgres-in-docker-with-ufw-in-ubuntu</link>
    <description>
Recently, I ran into a perplexing issue with my Ubuntu server. I had set up a deny rule for port 5432, which is the port PostgreSQL uses. Surprisingly, I could still access my PostgreSQL server from an external connection.

After some research, I discovered that this behavior is tied to how Docker manages traffic rules and interacts with UFW (Uncomplicated Firewall). In this post, I&#39;ll walk you through the problem, the underlying issue, and the easiest solution to block external access to your PostgreSQL database running inside a Docker container.

## The Problem

I have a PostgreSQL database running inside a Docker container on an Ubuntu server. To secure my setup, I used UFW to deny incoming traffic on port 5432:

```sh
sudo ufw deny 5432
```

Despite this rule, the PostgreSQL server was still accessible from outside the host. This behavior was unexpected and concerning.

## The Underlying Issue

Upon digging deeper, I found a comment on a GitHub issue [here](https://github.com/moby</description>
    <pubDate>Fri, 14 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>database</category><category>postgres</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/easy-zoomable-image-with-shadcn-tailwind</guid>
    <title>How to render zoomable image with Shadcn UI and Tailwind CSS in Next.js</title>
    <link>https://www.nico.fyi/blog/easy-zoomable-image-with-shadcn-tailwind</link>
    <description>
[Shadcn/UI](https://ui.shadcn.com) has amazing UI components. Even when a component you want is not available, you can compose it using the available components.

For example, I needed a zoomable image component. When an image is clicked, it should display the image in a larger size. To do this, I just used the [Dialog](https://ui.shadcn.com/docs/components/dialog) component from Shadcn UI and the Image component from Next.js.

```tsx:zoomable-image.tsx
import Image from &#39;next/image&#39;
import { Dialog, DialogContent, DialogTrigger } from &#39;./ui/dialog&#39;
import { DetailedHTMLProps, ImgHTMLAttributes } from &#39;react&#39;

export default function ZoomableImage({
  src,
  alt,
  className,
}: DetailedHTMLProps&lt;ImgHTMLAttributes&lt;HTMLImageElement&gt;, HTMLImageElement&gt;) {
  if (!src) return null
  return (
    &lt;Dialog&gt;
      &lt;DialogTrigger asChild&gt;
        &lt;Image
          src={src}
          alt={alt || &#39;&#39;}
          sizes=&quot;100vw&quot;
          className={className}
          style={{
            width: &#39;1</description>
    <pubDate>Wed, 12 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>tailwind</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/user-friendly-ai-chatbot-with-vercel-ai-sdk-and-gpt</guid>
    <title>Making a User-Friendly, Smart, and Secure AI Assistant Chatbot</title>
    <link>https://www.nico.fyi/blog/user-friendly-ai-chatbot-with-vercel-ai-sdk-and-gpt</link>
    <description>
I recently put together a [demo](https://x.com/2co_p/status/1799076368631906451) of an AI Bank assistant chatbot using [Vercel&#39;s AI SDK](https://sdk.vercel.ai/docs/introduction) and gpt-4o. This chatbot uses two main tools: `transferMoney` and `getBalance`. Here&#39;s a breakdown of what makes it user-friendly, secure, and how easy it is to implement.

## Smart Information Handling

One of the nice features of this chatbot is how it handles information. **Whether you prefer to enter your details bit by bit, all at once, or step-by-step, the chatbot can manage it**.

![](/static/images/articles/ai-sdk-chatbot-1.png.webp)

It won&#39;t execute the task until it has all the required information, **making the interaction feel more natural and less like filling out a form**. I absolutely believe that this is an important user experience when it comes to AI chatbots.

Implementing this kind of smart information handling can be quite challenging with other tools like [Langchain](https://www.langchai</description>
    <pubDate>Mon, 10 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>ai</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-correctly-display-list-of-languages</guid>
    <title>How to correctly display a list of languages</title>
    <link>https://www.nico.fyi/blog/how-to-correctly-display-list-of-languages</link>
    <description>
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from &#39;@/components/ui/card&#39;
import { Label } from &#39;@/components/ui/label&#39;

When displaying a list of languages for user selection, avoid the common mistake of showing language names in the currently selected language.

For instance, **if the current language is English** and your website supports **English, German, Japanese, and French**, it&#39;s incorrect to show the list like this:

&lt;Card&gt;
  &lt;CardHeader&gt;
    &lt;CardTitle&gt;Languages&lt;/CardTitle&gt;
    &lt;CardDescription&gt;Select a language&lt;/CardDescription&gt;
  &lt;/CardHeader&gt;
  &lt;CardContent&gt;
    &lt;LanguageSelectDemo /&gt;
  &lt;/CardContent&gt;
&lt;/Card&gt;

Instead, show the language names in their native languages. For example:

&lt;Card&gt;
  &lt;CardHeader&gt;
    &lt;CardTitle&gt;Languages&lt;/CardTitle&gt;
    &lt;CardDescription&gt;Select a language&lt;/CardDescription&gt;
  &lt;/CardHeader&gt;
  &lt;CardContent&gt;
    &lt;LanguageSelectDemo
      languages={[&#39;en&#39;, &#39;de&#39;, &#39;ja&#39;, &#39;fr&#39;].map((l) =&gt; {
        return {
          value: l,
      </description>
    <pubDate>Fri, 07 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category><category>javascript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/nesktop-make-desktop-app-with-next-js</guid>
    <title>Nesktop: Make an offline desktop app with Next.js</title>
    <link>https://www.nico.fyi/blog/nesktop-make-desktop-app-with-next-js</link>
    <description>
I wanted to have a GUI app for my personal project that required access to the file system. Ideally, I should create a desktop app using a native platform (e.g., Swift for macOS) or Electron for cross-platform support. However, developing a native app for macOS, Windows, and Linux would have taken time. The same applies to Electron, even though it&#39;s web-based.

So I had an idea. Since I am so used to making web apps using Next.js, why don&#39;t I make a Next.js app that anyone can run locally without _manually_ cloning and installing dependencies? It&#39;s basically like an Electron app but without the hassle of [managing window lifecycle](https://www.electronjs.org/docs/latest/tutorial/tutorial-first-app#managing-your-apps-window-lifecycle), [learning the process model](https://www.electronjs.org/docs/latest/tutorial/process-model), [packaging and code signing](https://www.electronjs.org/docs/latest/tutorial/distribution-overview), etc.

# Introducing: Nesktop

![](/static/images/articles/ne</description>
    <pubDate>Tue, 04 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>cli</category><category>open source</category><category>next.js</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/max-automatic-roundtrips-vercel-ai-sdk-simplify-tool-calling</guid>
    <title>The new maxAutomaticRoundtrips in Vercel AI SDK</title>
    <link>https://www.nico.fyi/blog/max-automatic-roundtrips-vercel-ai-sdk-simplify-tool-calling</link>
    <description>
Last month I wrote about [how to feed the result of function calls back to the model](/blog/vercel-ai-function-call-tool-example) using the [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction). The team at Vercel have just released a new version (3.1.22) of the SDK with a new feature called [`maxAutomaticRoundtrips`](https://sdk.vercel.ai/docs/reference/ai-sdk-core/generate-text#max-automatic-roundtrips). This feature simplifies tool calling and feeds the result back to the model.

Here&#39;s an example of how we needed to do it before:

```typescript
const { text, toolResults, toolCalls } = await generateText({
  ...context,
  messages,
})

// if there&#39;s a tool call, add it to the assistant&#39;s message
if (toolResults &amp;&amp; toolCalls) {
  messages.push({
    role: &#39;assistant&#39; as const,
    content: toolCalls,
  })

  messages.push({
    role: &#39;tool&#39; as const,
    content: toolResults,
  })

  const { text: finalText } = await generateText({
    ...context,
    messages,
  })
  messages.pu</description>
    <pubDate>Mon, 03 Jun 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>ai</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/simple-i18n-next-update</guid>
    <title>Simple i18n for Next.js is smarter now</title>
    <link>https://www.nico.fyi/blog/simple-i18n-next-update</link>
    <description>
On the weekend, I added two new features to the [simple-i18n-next](https://github.com/nicnocquee/simple-i18n-next) CLI tool. The first one is to support nested keys. The second one is to support multiple JSON files in each language directory.

## Nested keys

Now you can use nested keys in your translation files. For example, if you have a translation file like this:

```json:locales/en/messages.json
{
  &quot;landing&quot;: {
    &quot;title&quot;: &quot;Welcome to my website!&quot;,
    &quot;subtitle&quot;: &quot;This is a subtitle&quot;
  }
}
```

The CLI will generate two constants you can use in your code: `landingTitle` and `landingSubtitle`. You can use them like this:

```tsx:page.tsx
import { landingTitle, landingSubtitle } from &#39;@/locales/.generated/server&#39;

export default function Home() {
  return (
    &lt;main&gt;
      &lt;h1&gt;{landingTitle}&lt;/h1&gt;
      &lt;p&gt;{landingSubtitle}&lt;/p&gt;
    &lt;/main&gt;
  )
}
```

## Multiple JSON files

[Originally](/blog/internationalization-for-next-js), the CLI read only the `messages.json` files in each </description>
    <pubDate>Mon, 27 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>cli</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/simple-i18n-next-with-plurals-support</guid>
    <title>Simple Internationalization for Next.js with Plurals Support</title>
    <link>https://www.nico.fyi/blog/simple-i18n-next-with-plurals-support</link>
    <description>
After releasing my [simple-i18n-next](https://github.com/nicnocquee/simple-i18n-next) tool [last week](/blog/internationalization-for-next-js), [Václav Hodek](https://x.com/vaclavhodek) from [Localazy](https://localazy.com) asked if it supported pluralization. I hadn&#39;t thought about it before, but I thought it would be a good idea to add it.

So I started by first researching how other tools support pluralization. It turned out that it&#39;s not as simple as handling &quot;an apple&quot; and &quot;two apples.&quot; _It&#39;s more complicated than that_. For example, in German, you can have &quot;ein Apfel&quot; or &quot;zwei Äpfel,&quot; just like in English. Meanwhile, in Japanese, there are no different forms for &quot;one apple&quot; and &quot;two apples,&quot; so you have to use the same form for both: リンゴ1個 and リンゴ2個. Other languages like Arabic even have 6 different plural forms!

The complexity doesn&#39;t stop there. **There are also different plural forms for the ordinal numbers**! For example, in English, you can have &quot;1st apple,&quot; &quot;2nd apple,&quot; &quot;</description>
    <pubDate>Wed, 22 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>cli</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-easily-secure-next-js-projects</guid>
    <title>How to easily secure Next.js projects</title>
    <link>https://www.nico.fyi/blog/how-to-easily-secure-next-js-projects</link>
    <description>
Last weekend we had a small internal hackathon at Hyperjump to create an AI chat bot. I used the stack I&#39;m most familiar with—Next.js, TypeScript, Tailwind CSS and OpenAI. When the project reached a good enough version, I could simply share the link to the deployed project so that everyone could try it out. However, I don&#39;t want people outside the team to be able to access it, as bad actors might abuse it and run up my OpenAI bill.

But the thing is, I don&#39;t want to implement a whole authentication system just to protect my demo project.

If I were using Vercel&#39;s Enterprise plan, I could have easily protected the deployments. But I didn&#39;t. So I decided to implement the simplest solution I could think of: [**basic access authentication**](https://en.wikipedia.org/wiki/Basic_access_authentication).

All I had to do was add this code to `middleware.ts`:

```typescript:middleware.ts showLineNumbers
import { NextRequest, NextResponse } from &quot;next/server&quot;;

export default function middlewar</description>
    <pubDate>Mon, 20 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-correctly-split-string-into-words-in-javascript</guid>
    <title>How to correctly split a string into words in JavaScript</title>
    <link>https://www.nico.fyi/blog/how-to-correctly-split-string-into-words-in-javascript</link>
    <description>
If you have been using `.split(&#39; &#39;)` to get an array of words from a string, you should stop doing so. It works perfectly for English, but it&#39;s not the best solution for other languages.

For example, if you have a string like this:

```typescript
const str = &#39;俺はルフィ！海賊王になる男だ！&#39;
console.table(str.split(&#39; &#39;))
```

you will get this:

![](/static/images/articles/split-japanese.png.webp)

which is not what we want. That&#39;s the whole sentence.

Instead, we should use the [Intl.Segmenter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter)!

```typescript
const str = &#39;俺はルフィ！海賊王になる男だ！&#39;
const segmenterJa = new Intl.Segmenter(&#39;ja-JP&#39;, { granularity: &#39;word&#39; })

const segments = segmenterJa.segment(str)
console.table(Array.from(segments))
```

![](/static/images/articles/intl-segmenter.png.webp)

We can also use `{ granularity: &#39;sentence&#39; }` to get the sentences in the string.

![](/static/images/articles/intl-segmenter-sentence.png.webp)

---

By the w</description>
    <pubDate>Thu, 16 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/internationalization-for-next-js</guid>
    <title>Internationalization for Next.js Without Hassle</title>
    <link>https://www.nico.fyi/blog/internationalization-for-next-js</link>
    <description>
For developers who live in a country with multiple national languages like Switzerland, internationalization (i18n) is a must-have and is usually implemented from day 1. For example, in the websites I made for the company I work for, like the [iTheorie website](https://www.itheorie.ch/de), they need to have all four languages—English, German, French, and Italian—since the beginning.

![](/static/images/articles/react-meetup.png.webp)

I usually use the solution from [next-18next](https://github.com/i18next/next-i18next). But a few weeks ago, I found out about [Paraglide JS](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) when I attended a React meetup in Zürich. I like the idea of having tree-shakeable messages when using Paraglide JS. This means each page only loads the messages it actually uses. Unfortunately, I couldn&#39;t successfully use it with my Next.js project because of several errors. Not to mention, there are unfortunately too many typos in the [documentation](https</description>
    <pubDate>Tue, 14 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>cli</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/stop-using-environment-variable-directly</guid>
    <title>Stop using environment variable directly in your TypeScript code</title>
    <link>https://www.nico.fyi/blog/stop-using-environment-variable-directly</link>
    <description>
Environment variables are a must-have in web development. They help us manage different configurations between development, testing, and production without hardcoding sensitive data like database URLs or API keys. They&#39;re flexible, secure, and help keep our applications modular.

## The Problem with Direct Access

It might seem like a good idea to directly access `process.env` in your TypeScript code, but there are a few problems with it. **First off, it can lead to typos in your variable names that go unnoticed until runtime**, since they&#39;re just strings with no type checking. Additionally, using environment variables directly doesn&#39;t give you IntelliSense support or auto-completion in your IDE, making your development process more prone to errors and slower.

Also, if you use environment variables directly, you need to do a lot of type casting and type checking in your code. Do you recognise code like this?

```typescript
const apiKey = process.env.NEXT_PUBLIC_API_KEY! as string
```</description>
    <pubDate>Mon, 13 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>cli</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-unit-test-nextjs-api-route</guid>
    <title>How to unit test Next.js API route</title>
    <link>https://www.nico.fyi/blog/how-to-unit-test-nextjs-api-route</link>
    <description>
[Zaher Ghaibeh](https://twitter.com/zaherg) asked me how to unit test a [Next.js API route](https://nextjs.org/docs/app/building-your-application/routing/route-handlers). But he ended up figuring it out for himself. And he&#39;s kind enough to share his solution with me in this [repository](https://github.com/zaherg/with-vitest).

His solution is to use [Vitest](https://vitest.dev) and [MSW (Mock Service Worker)](https://mswjs.io). Vitest is a test runner for JavaScript and TypeScript that makes it easy to write and run tests against your code. MSW is a mocking library that allows you to mock HTTP requests and responses in your tests.

The idea is quite simple:

1. Create a dummy request based on the [NextRequest](https://nextjs.org/docs/app/api-reference/functions/next-request).
2. Pass the mock request to the handler of the API route (GET, POST, etc).
3. Assert that the response is what you expect.
4. If necessary, intercept any requests made within the handler and mock them using MSW.
</description>
    <pubDate>Thu, 09 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>test</category><category>vitest</category><category>msw</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-show-popular-posts-using-posthog</guid>
    <title>How to show popular posts using PostHog</title>
    <link>https://www.nico.fyi/blog/how-to-show-popular-posts-using-posthog</link>
    <description>
This website is using [Posthog](https://eu.posthog.com) for collecting analytics data. When I first heard about it, I thought it was just another complicated web analytics tool. But I was wrong. Posthog is a powerful tool that can help you understand your users better and make data-driven decisions.

**It also has an API that allows me to get data from Posthog in my own website**. I&#39;m using it to show the most popular posts on my blog. Here&#39;s how you can do it too.

First, you need to sign up to Posthog. It has a free plan that allows you to collect data for free for up to 1 million events per month. What&#39;s important is that you can set a monthly billing limit!

After that, create a new project. It&#39;s not difficult. Just follow the instructions on the website. Once you&#39;ve got the project set up and the data is starting to come in, you need to create a new insight. The most important thing is to choose &quot;Path Name&quot; in the &quot;Breakdown by&quot; field. Don&#39;t forget to &quot;Save&quot; the new insight.

![]</description>
    <pubDate>Wed, 08 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-use-page-screenshot-as-open-graph-image</guid>
    <title>How to use a page or website screenshot as an Open Graph image</title>
    <link>https://www.nico.fyi/blog/how-to-use-page-screenshot-as-open-graph-image</link>
    <description>
Some people argue that using a screenshot of a page as an [open graph](https://ogp.me) image is a great idea that can increase traffic. That&#39;s why several [SaaS](https://en.wikipedia.org/wiki/Software_as_a_service) options offer this service. For example, [ScreenshotOne](https://screenshotone.com) and [Image.social](https://image.social). If you have tried them, you&#39;ll find that they are not so cheap.

I understand that it costs money to run this type of service. Capturing a screenshot involves using a real browser. And running a browser to capture tons of screenshots requires a lot of computational power.

But for small bloggers like me, I&#39;m not sure if it&#39;s worth the money to use those services. **Once a blog post is published, I just need to generate the screenshot of the page once**. I think it&#39;s a waste of money to pay every month for something that I won&#39;t use too often. Not to mention that I have a computer that should be powerful enough to perform this task.

So, I created a C</description>
    <pubDate>Tue, 07 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>cli</category><category>open source</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/animated-nav-link-tabs-like-vercel</guid>
    <title>How to have animated nav tabs with React and Tailwind CSS</title>
    <link>https://www.nico.fyi/blog/animated-nav-link-tabs-like-vercel</link>
    <description>
&lt;iframe
  title=&quot;video&quot;
  src=&quot;https://player.cloudinary.com/embed/?public_id=Screen_Recording_2024-05-04_at_08.59.13_wbcv4t&amp;cloud_name=dlampxaex&amp;player[muted]=false&amp;player[autoplay]=true&amp;player[loop]=true&quot;
  width=&quot;640&quot;
  height=&quot;360&quot;
  style={{ height: &#39;auto&#39;, width: &#39;100%&#39;, aspectRatio: 640 / 360 }}
  allow=&quot;autoplay; fullscreen; encrypted-media; picture-in-picture&quot;
&gt;&lt;/iframe&gt;

I was intrigued by the navigation tabs in Vercel&#39;s dashboard, as shown in the video above. When the user clicks on a tab, the tab indicator animates to the position of the selected tab and also animates the width to match the width of the selected tab. It also displays a hover background that animates to the position of the hovered tab. It&#39;s so slick.

So I made one using [Tailwind CSS](https://tailwindcss.com) and [Open Props](/blog/how-to-use-open-props-with-tailwind) for the animation.

I honestly don&#39;t know how to do this with just CSS. I don&#39;t think it&#39;s possible. So I did the obvious. I set the positio</description>
    <pubDate>Mon, 06 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>react</category><category>tailwind</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/vercel-ai-function-call-tool-example</guid>
    <title>How to Feed the Function Call Result Back to the Model with Vercel AI SDK</title>
    <link>https://www.nico.fyi/blog/vercel-ai-function-call-tool-example</link>
    <description>
Vercel have just released a new version of their [AI SDK along with new documentation](https://sdk.vercel.ai/docs/introduction). It&#39;s better than the previous documentation, but it still lacks an example of how to use the SDK to [call functions](https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling) and then [feed the result back to the model](https://platform.openai.com/docs/guides/function-calling/parallel-function-calling).

For example, the `getWeather` function returns a string that is immediately displayed to the user in [their weather example](https://sdk.vercel.ai/examples/next-app/tools/call-functions-in-parallel#server).

```typescript
const { text, toolResults } = await generateText({
  model: openai(&#39;gpt-3.5-turbo&#39;),
  system: &#39;You are a friendly weather assistant!&#39;,
  messages: history,
  tools: {
    getWeather: {
      description: &#39;Get the weather for a location&#39;,
      parameters: z.object({
        city: z.string().describe(&#39;The city to get the weather for&#39;),</description>
    <pubDate>Fri, 03 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>ai</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-stop-rejections-from-bringing-you-down-by-reframing</guid>
    <title>How to stop rejections from bringing you down</title>
    <link>https://www.nico.fyi/blog/how-to-stop-rejections-from-bringing-you-down-by-reframing</link>
    <description>
Facing rejection while pursuing our goals—like landing that job or winning that pitch—often triggers tough emotional responses. We may feel devastated, begin to doubt our abilities, or even question our worth. These feelings can severely impact our motivation and self-esteem.

But what if we could change our approach? By **reframing** our objective from merely achieving success to learning from each experience, we can transform the way we handle rejection.

## Setting a New Goal

Before you step into an interview or pitch your project, try reframing your primary aim. **Instead of focusing solely on securing the job, prioritize discovering what you can improve upon**. This perspective turns every outcome, whether a rejection or an acceptance, into a valuable learning opportunity.

## Learning Not Earning

View a job interview, for instance, not as a barrier to overcome, but as a learning session. What questions did they ask that stumped you? How could your responses be sharpened? This </description>
    <pubDate>Thu, 02 May 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>philosophy</category><category>personal</category><category>thought</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-clone-only-specific-folder-in-git-repo</guid>
    <title>How to clone only specific folder in Git repo</title>
    <link>https://www.nico.fyi/blog/how-to-clone-only-specific-folder-in-git-repo</link>
    <description>
Many open source projects include some examples in the same repository as the main code. This is common practice in the open source community. However, as a user of the project, you may only need some of the examples. In this blog post, I&#39;ll show you how to clone only the folders you need.

There&#39;s no direct way to clone only specific folders in the Git repo. To get just the folders you need, you have to

1. clone the repo to the specified destination directory with minimal depth and no tree.
2. set up the [sparse checkout](https://git-scm.com/docs/git-sparse-checkout)
3. run the `git checkout`.

This script will do the above:

```shell
#!/bin/bash

# Check if the correct number of arguments are passed
if [ &quot;$#&quot; -ne 3 ]; then
    echo &quot;Usage: $0 &lt;git-url&gt; &lt;path-to-directory&gt; &lt;target-directory&gt;&quot;
    exit 1
fi

# Variables from arguments
GIT_URL=$1
DIRECTORY_PATH=$2
TARGET_DIR=$3

# Cleanup function to remove the target directory on failure
cleanup() {
    echo &quot;An error occurred. Clean</description>
    <pubDate>Tue, 30 Apr 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>git</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-i-accidentally-increase-vercel-usage</guid>
    <title>How I accidentally increase Vercel usage</title>
    <link>https://www.nico.fyi/blog/how-i-accidentally-increase-vercel-usage</link>
    <description>
Last week I received an email from Vercel saying that many metrics in my project had increased and exceeded the hobby plan limit, including the [Function Invocations](https://vercel.com/docs/pricing/serverless-functions#managing-function-invocations).

![](/static/images/articles/vercel2.png.webp)

This surprised me because while my blog has more visitors than before, there&#39;s no way that many serverless functions are being called in my project. So what actually happened?

After looking at the log in the Vercel dashboard, I noticed that the middleware and two other routes were being called a lot.

The irregularity with the middleware and one of the routes, the `/tags/[tag]` route, is related. This happened because I made a mistake a few days ago where I accidentally created a ton of wrong tags. These tag pages were unfortunately crawled by bots. That&#39;s why there are a lot of calls to the middleware and the `/tags/[tag]` route.

The fixes for these are

1. Exclude the `/tags/[tag]` rout</description>
    <pubDate>Mon, 29 Apr 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>vercel</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/react-strict-mode-and-race-condition</guid>
    <title>React Strict Mode and Race Condition</title>
    <link>https://www.nico.fyi/blog/react-strict-mode-and-race-condition</link>
    <description>
One of the main reasons React introduced [Strict Mode](https://react.dev/reference/react/StrictMode) is to help us find hard-to-notice bugs, like race conditions. One of the ways it does this is by re-running [Effects](https://react.dev/learn/synchronizing-with-effects) twice in development mode.

When I created the [aborting fetch demo](/experiments/fetch-abort-demo) in the [previous post](/blog/cancel-fetch-js-correctly), I stumbled upon a race condition bug which I had neither noticed nor understood at the beginning. This was the buggy code:

```typescript
function MyComponent() {
  // in production, don&#39;t use multiple states like this
  const [loading, setLoading] = useState(false)
  const [data, setData] = useState&lt;any&gt;(null)
  const [error, setError] = useState&lt;any&gt;(null)
  const abortControllerRef = useRef&lt;AbortController | null&gt;(null)

  useEffect(() =&gt; {
    const abortController = new AbortController()
    abortControllerRef.current = abortController

    const fetchData = a</description>
    <pubDate>Thu, 25 Apr 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/cancel-fetch-js-correctly</guid>
    <title>How to cancel fetch on demand OR with timeout</title>
    <link>https://www.nico.fyi/blog/cancel-fetch-js-correctly</link>
    <description>
By default, the [web fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) doesn’t have a timeout option. This means that a request will never be cancelled unless it&#39;s explicitly cancelled. So, how do we cancel a fetch request on demand? And how do we set a timeout for a fetch request? Taking it even further, how do we allow a user to **cancel the fetch before the timeout is reached**?

When I asked ChatGPT about the default timeout for fetch, here&#39;s what it said:

&gt; The `fetch` API in browsers does not have a built-in timeout mechanism. By default, a `fetch` request will run indefinitely until the server responds or the connection is closed by some network error. If you need to implement a timeout, you have to do it manually with `setTimeout` and abort the fetch using an `AbortController`. Here’s a quick example:

```typescript
const controller = new AbortController()
const timeoutId = setTimeout(() =&gt; controller.abort(), 5000) // Set timeout to 5000 ms

fetch(&#39;https://e</description>
    <pubDate>Wed, 24 Apr 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/you-dont-need-usestate-in-react</guid>
    <title>You don&#39;t need useState in React</title>
    <link>https://www.nico.fyi/blog/you-dont-need-usestate-in-react</link>
    <description>
In one of the pull requests I reviewed, I noticed a pattern that I&#39;ve seen in many pull requests. A React component had multiple UI states such as `loading`, `error`, and `success`. The author used multiple `useState` hooks to manage these states, which resulted in code that is hard to read and error-prone, for example:

```typescript
const MyComponent = () =&gt; {
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(false)
  const [success, setSuccess] = useState(false)

  return (
    &lt;div&gt;
      {loading &amp;&amp; !error &amp;&amp; !success &amp;&amp; &lt;p&gt;Loading...&lt;/p&gt;}
      {error &amp;&amp; !loading &amp;&amp; !success &amp;&amp; &lt;p&gt;Error occurred&lt;/p&gt;}
      {success &amp;&amp; !loading &amp;&amp; !error &amp;&amp; &lt;p&gt;Operation completed successfully&lt;/p&gt;}
    &lt;/div&gt;
  )
}
```

These states are **distinct from each other**. When `loading` is true, the `error` and `success` states should be `false`. Using multiple `useState` hooks can cause unexpected behaviors, like accidentally setting two states to `true` simultaneousl</description>
    <pubDate>Tue, 23 Apr 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/server-only-package</guid>
    <title>server-only package is empty?!</title>
    <link>https://www.nico.fyi/blog/server-only-package</link>
    <description>
If you&#39;ve read the Next.js documentation, you&#39;ll know about [server-only package](https://www.npmjs.com/package/server-only) since it&#39;s mentioned a few times in the doc. Using this package, you can [keep server-only code out of the client component](https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#keeping-server-only-code-out-of-the-client-environment). I was kinda curious about this package and how it works and my discovery was a bit surprising.

First, I need to find out where the source code for this package is. This happened to be quiet a challenge because the package basically has no information whatsoever in the NPM registry. Not to mention there&#39;s no link to the source code. It only has a link to [reactjs.org](https://reactjs.org).

I found out later on that the source code is actually in [Next.js repository](https://github.com/vercel/next.js/tree/canary/packages/next/src/compiled/server-only). But to my surprise, this package only has 3 file</description>
    <pubDate>Wed, 17 Apr 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/understanding-layout-and-template-next-app-router</guid>
    <title>Understanding Layout and Template Next.js App Router</title>
    <link>https://www.nico.fyi/blog/understanding-layout-and-template-next-app-router</link>
    <description>
Next.js with App Router gives us two options to wrap pages: [Layout](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts) and [Template](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#templates). The documentation makes it seem like Layout and Template are simple. But after playing with them for a while, I realized that they are not.

Let&#39;s say we have a layout component as follows:

```typescript
// /app/layout.tsx
import Time from &#39;./time&#39;
import { getServerTime } from &#39;../get-server-time&#39;
import SearchField from &#39;../search-field&#39;

export default async function Layout({ children }: { children: React.ReactNode }) {
  const time = await getServerTime(false)
  return (
    &lt;div className=&quot;flex h-screen flex-col space-y-2 p-4 font-sans text-black&quot;&gt;
      &lt;div className=&quot;space-y-2 pb-8 pt-6 md:space-y-5&quot;&gt;
        &lt;h1 className=&quot;text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl</description>
    <pubDate>Tue, 16 Apr 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/workaround-layout-has-no-search-params</guid>
    <title>Overcoming Next.js&#39; Search Params Limitation in Layouts</title>
    <link>https://www.nico.fyi/blog/workaround-layout-has-no-search-params</link>
    <description>
In Next.js App Router, the [Layout component doesn&#39;t have access to the search params](https://nextjs.org/docs/app/api-reference/file-conventions/layout#layouts-do-not-receive-searchparams) because a shared layout is not re-rendered during navigation, which could lead to stale `searchParams` between navigations. This limitation has caused many developers to believe that Next.js App Router is not a good choice for applications where layout is used to display information based on the value in the search params.

For example, you may have a user impersonation feature where the admins of your app can impersonate other users by changing the value of the `user` search param. Then you want to display the user&#39;s name in the layout. However, the layout doesn&#39;t have access to the search params because it&#39;s a shared layout. Only page components get the search params.

The answer to this predicament is to use [parallel routes](https://nextjs.org/docs/app/building-your-application/routing/parallel</description>
    <pubDate>Mon, 15 Apr 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/should-you-use-vercel</guid>
    <title>Should you use Vercel?</title>
    <link>https://www.nico.fyi/blog/should-you-use-vercel</link>
    <description>
![](/static/images/articles/ogimage.jpg.webp)

Vercel&#39;s recent pricing changes have caused quite a stir among developers. While some will be charged more, others will actually see a reduction in their bills.

The confusion was sparked by an email stating that customer feedback regarding the high prices was heard, yet it announced that customers would need to pay more in the following months.

In light of this, I created a simple tool to help you decide if **Vercel is the right choice for you**. Check it out here: [https://www.nico.fyi/should-you-use-vercel](/should-you-use-vercel).

After releasing the tool, [Lee Robinson](https://twitter.com/leeerob) from Vercel kindly reminded me that Vercel supports [38 web frameworks](https://vercel.com/docs/frameworks/more-frameworks), not just Next.js and Remix. I&#39;ve updated the tool to include a list of supported frameworks.

Furthermore, the community has informed me about some tools for self-hosting your app. Here are some recommendations:

-</description>
    <pubDate>Tue, 09 Apr 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>vercel</category><category>coolify</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/html-input-autocomplete-section</guid>
    <title>A Simple Guide to Using section-* in HTML Forms for Autocomplete</title>
    <link>https://www.nico.fyi/blog/html-input-autocomplete-section</link>
    <description>
Ever filled out an online form and noticed how it tries to guess the rest of your information? That&#39;s autocomplete in action—a handy tool that makes filling out forms faster. But when you have a big form, things can get tricky. That&#39;s where &quot;section-\*&quot; comes into play.

## What&#39;s This &quot;section-\*&quot; Thing Anyway?

Think of a form on a website. Sometimes, these forms ask for a lot of the same info but in different parts. Let&#39;s say you&#39;re buying something online. You&#39;ve got a section for your shipping address and another one for your billing address. Both ask for similar details—where you live, your city, and so on. Without some guidance, your browser might get confused and mix things up.

That&#39;s where &quot;section-\*&quot; steps in. It&#39;s a simple way to tell your browser, **&quot;Hey, keep these parts separate when you&#39;re trying to help out with autocomplete.&quot;**

## Breaking It Down

The &quot;section-&quot; part is your cue to the browser that you&#39;re starting a new section. The &quot;\*&quot; is where you get to be spe</description>
    <pubDate>Fri, 29 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>html</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/next-js-multiple-root-layout</guid>
    <title>How to have multiple root layouts in Next.js with App Router</title>
    <link>https://www.nico.fyi/blog/next-js-multiple-root-layout</link>
    <description>
Recently, [shadcn](https://twitter.com/shadcn) introduced a cool update in Shadcn/ui called [Blocks](https://ui.shadcn.com/blocks), which includes ready-to-use layouts such as admin dashboards with a sidebar, marketing sections, and more. This caught my attention, and I decided to give it a try on this blog. Creating a new page and adding a block is simple. However, there&#39;s a slight issue with my blog&#39;s root layout. It uses a [SectionContainer](https://github.com/nicnocquee/nico.fyi/blob/60661d8a4403883063d491dfc77b04e39642c31f/components/SectionContainer.tsx#L9) component that restricts the page width to `max-w-3xl`. This constraint caused the blocks to appear off.

![](/static/images/articles/blocks-weird.png.webp)

I figured I needed a different layout for pages showcasing these blocks. There are a couple of ways to handle this. One option is to simplify the root layout and assign the `SectionContainer`-based layout to specific blog routes. But I prefer not to split the blog&#39;s layo</description>
    <pubDate>Thu, 28 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/monty-hall-problem</guid>
    <title>Simple guide to Monty Hall problem</title>
    <link>https://www.nico.fyi/blog/monty-hall-problem</link>
    <description>
Ever stumbled upon a probability puzzle that seems counterintuitive at first glance but is actually grounded in solid math? Welcome to the Monty Hall Problem. It&#39;s named after the host of the game show &quot;Let&#39;s Make a Deal,&quot; where contestants picked doors to win prizes. Here’s the lowdown on this fascinating problem and why it messes with our intuition.

![](/static/images/articles/monty-hall.png.webp)

Imagine you&#39;re on a game show. You&#39;re presented with three doors: behind one is a car (the prize you want), and behind the others, goats. You pick a door, say number one. Before revealing what&#39;s behind it, the host, who knows what&#39;s behind each door, opens another door, say number three, to reveal a goat. He then asks if you&#39;d like to stick with your original choice or switch to the remaining unopened door. What would you do? Stick or switch?

Intuitively, it might seem like it doesn&#39;t matter. There are two doors left, so it feels like a 50/50 chance, right? Wrong. And here&#39;s why.

When </description>
    <pubDate>Wed, 27 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>philosophy</category><category>personal</category><category>thought</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/temporal-dead-zone-javascript</guid>
    <title>Temporal Dead Zone in Javascript</title>
    <link>https://www.nico.fyi/blog/temporal-dead-zone-javascript</link>
    <description>
Recently, I encountered a term on Twitter that piqued my curiosity— the [Temporal Dead Zone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) (TDZ) in JavaScript. Despite its complex-sounding name, the concept behind TDZ is straightforward and plays a crucial role in how JavaScript handles variable declarations.

When you declare variables in JavaScript using `let` and `const`, these declarations introduce a so-called Temporal Dead Zone. But what does that mean? Essentially, **it&#39;s a fancy way of saying there&#39;s a period where the variable exists but can&#39;t be used just yet**. If you try to access the variable in this no-man&#39;s land, JavaScript throws a reference error. It&#39;s there, but not quite ready for action.

For instance, attempting to log `myVar` before declaring it with `let` causes a `ReferenceError`, showing that you have entered the TDZ:

```javascript
console.log(myVar) // ReferenceError: Cannot access &#39;myVar&#39; before initialization
let myVar =</description>
    <pubDate>Tue, 26 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>javascript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/technocentrism-caused-falsely-assume-deaf</guid>
    <title>Technocentrism Caused Me to Falsely Assume About the Deaf</title>
    <link>https://www.nico.fyi/blog/technocentrism-caused-falsely-assume-deaf</link>
    <description>
During Hyperjump&#39;s weekly sharing session, my colleague [Kevin](https://twitter.com/kvnhermawan) shared a perspective I hadn&#39;t considered before. He recounted a conversation with his friend, a sign language interpreter, about the potential impact of AI on their profession. With the rapid advancement of AI technologies, Kevin wondered if his friend felt his job might be at risk in the foreseeable future.

Kevin&#39;s friend shared an insightful example that reshaped our understanding. He described a scenario familiar to many of us. At a conference, organizers use AI to transcribe speakers&#39; words into subtitles in real-time. It seems to bridge the gap for deaf attendees. On the surface, this technological solution appears to address accessibility concerns effectively. But, **this assumption overlooks the deaf community&#39;s nuanced needs. It also ignores the rich, cultural depth of sign language as a form of communication**.

The interpreter explained that while live subtitles seem thorough, t</description>
    <pubDate>Mon, 25 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>philosophy</category><category>personal</category><category>thought</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-report-bug</guid>
    <title>How to report bugs to developers</title>
    <link>https://www.nico.fyi/blog/how-to-report-bug</link>
    <description>
You found a problem in an app and are about to tell the developer. But before you dash off a quick &quot;Hey, it&#39;s not working&quot; message, remember that software can be pretty tricky. An app might act differently on various devices, web browsers, or operating systems — there&#39;s no one-size-fits-all rule. So, if you&#39;re ready to jump in and tell about a bug (even if you&#39;re not a tech whiz), this guide is perfect for you.

Also, if you&#39;re a developer and think this guide is helpful, make sure to **share it with your friends and family who aren&#39;t great with tech**. It&#39;ll definitely help cut down on those confusing &quot;it&#39;s broken&quot; texts later on.

Here&#39;s how you can get that pesky bug squashed quickly and efficiently:

1. **Craft a concise and descriptive title** - Get straight to the point but be specific. &quot;Upload function fails&quot; beats &quot;it&#39;s broken&quot; any day.
2. **Provide step-by-step reproduction steps** - The clearer, the better. Every detail counts.
3. **Detail your environment** - Device, operat</description>
    <pubDate>Fri, 22 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/javascript-tag-function</guid>
    <title>JavaScript Tag Functions</title>
    <link>https://www.nico.fyi/blog/javascript-tag-function</link>
    <description>
JavaScript is a language that never ceases to astonish with its flexibility and features. One of the lesser-known but incredibly powerful features of JavaScript is the [Tagged Template Literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates), commonly referred to as Tag Functions.

### What Are JavaScript Tag Functions?

JavaScript Tag Functions are an advanced feature introduced in ES6 (ECMAScript 6) that allows you to parse and manipulate template literals through a function. A template literal is a way to concatenate strings in JavaScript, which can include variables and expressions inside `${}` placeholders.

#### Syntax Basics:

```javascript
tagFunction`string text ${expression} string text`
```

In the above syntax, `tagFunction` is a function that is called with the processed parts of the template literal. The first argument of this function is an array of strings (the static parts), and the subsequent arguments are relate</description>
    <pubDate>Thu, 21 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>javascript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/populate-postgres-fake-data</guid>
    <title>Populate Postgres database with fake data</title>
    <link>https://www.nico.fyi/blog/populate-postgres-fake-data</link>
    <description>
During development and testing, it&#39;s common for us to set up the environment where our app runs, along with the necessary data. For instance, if we have a web store app and want to create a feature enabling users to receive notifications when an item they try to add to a cart is sold out, we need to efficiently develop this by using fake data that includes items already sold out in the database. This practice not only aids current development but also prepares us for future bug fixes or improvements related to this feature.

In the past, I used scripts with libraries such as [Faker](https://fakerjs.dev) to add fake data to the database. But now, I have found another way to insert data into Postgres using SQL.

**Setting the Stage for a Chat App**

Consider we&#39;re building a chat app capable of handling millions of messages daily. To get started, we set up two tables: one for the chats and another for the individual messages. Here&#39;s a quick look at the schema:

```sql
create table chats</description>
    <pubDate>Wed, 20 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>postgres</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/a-few-words-on-a-few-words-on-testing</guid>
    <title>A few words on &quot;A few words on testing&quot;</title>
    <link>https://www.nico.fyi/blog/a-few-words-on-a-few-words-on-testing</link>
    <description>
Tech Twitter got buzzing a few days back when [Thorsten Ball opened a discussion about the pains of testing](https://registerspill.thorstenball.com/p/a-few-words-on-testing)—how it&#39;s a time-suck to write, run, and debug them. Many devs nodded in agreement, finding tests more of a hassle than a help.

I&#39;ve got some thoughts on that too. While I&#39;m nodding along with the frustration over flaky tests, I don&#39;t exactly buy into the &quot;less is more&quot; approach to testing that Thorsten suggests, nor the idea that sheer developer dedication is the magic bullet. Let&#39;s dive into where I&#39;m coming from.

First off, it&#39;s a fact: tests can be a headache, especially when they fail for no apparent reason. We&#39;ve had our share of those at Hyperjump. But, more often than not, when our tests do fail, it&#39;s on us—they&#39;ve actually caught something we&#39;ve missed!

![Hyperjump failure report](/static/images/articles/hyperjump-failure-report.png.webp)

In my experience, when a test fails in CI but works fine on your</description>
    <pubDate>Tue, 19 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/will-humans-still-have-the-edge-over-ai</guid>
    <title>Will humans still have the edge over AI?</title>
    <link>https://www.nico.fyi/blog/will-humans-still-have-the-edge-over-ai</link>
    <description>
Recently, a splash was made in the tech community by the [unveiling of Devin](https://x.com/cognition_labs/status/1767548763134964000?s=20), a new AI software engineer created by Cognition Labs. Its creators claim that Devin not only aced practical engineering interviews at top AI firms but also completed actual projects on Upwork. This announcement has stirred a mixed bag of reactions within the developer community. While some critics highlighted bugs in Devin&#39;s demo or suggested the tasks were tailored to its training, others humorously wished future developers luck in maintaining Devin&#39;s code.

Yet, amidst the skepticism and jest, a significant conversation has emerged about the future of human creativity in the age of AI. A perspective, echoed by Indonesian developer [mgilangjanuar](https://twitter.com/mgilangjanuar), suggests that [while AI might replicate coding tasks, it cannot mimic the human capacity for generating ideas](https://x.com/mgilangjanuar/status/1767675844221444481</description>
    <pubDate>Mon, 18 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-scale-luck</guid>
    <title>How to scale luck</title>
    <link>https://www.nico.fyi/blog/how-to-scale-luck</link>
    <description>
[Anang Marjono recently asked on Twitter about how to scale luck](https://twitter.com/marjono__/status/1766025636873998557). Have you also ever wondered how to get lucky more often? The secret lies in a simple formula: **Luck equals preparation meets opportunity**. So to scale luck, you need to focus on two things—**preparing more and widening your network**.

## The Power of Preparation

Preparation is all about enhancing your skills and knowledge. For programmers or software developers, this means diving into new technologies, mastering coding challenges on platforms like LeetCode, or even refining soft skills such as effective communication and writing. Contrary to the stereotype of the solitary coder, programming is a collaborative field. It involves teamwork, client negotiations, and even convincing your bosses about your ideas.

But don’t stop at your professional domain. Broadening your horizons by learning new languages or maintaining a fitness routine can also play a crucial </description>
    <pubDate>Mon, 11 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/using-generator-function-in-react</guid>
    <title>Using generator function in React</title>
    <link>https://www.nico.fyi/blog/using-generator-function-in-react</link>
    <description>
Vercel recently announced the [new 3.0 version of their AI SDK](https://vercel.com/blog/ai-sdk-3-generative-ui) with support for Generative UI. What caught my eye was the new `render` method, where developers can map specific calls to React Server Components. Notably, it uses a [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) to sequentially render different components, as shown below:

```typescript
import { render } from &#39;ai/rsc&#39;
import OpenAI from &#39;openai&#39;
import { z } from &#39;zod&#39;

const openai = new OpenAI()

async function submitMessage(userInput) { // &#39;What is the weather in SF?&#39;
  &#39;use server&#39;

  return render({
    provider: openai,
    model: &#39;gpt-4&#39;,
    messages: [
      { role: &#39;system&#39;, content: &#39;You are a helpful assistant&#39; },
      { role: &#39;user&#39;, content: userInput }
    ],
    text: ({ content }) =&gt; &lt;p&gt;{content}&lt;/p&gt;,
    tools: {
      get_city_weather: {
        description: &#39;Get the current weather for a cit</description>
    <pubDate>Fri, 08 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/the-problem-with-next-js-14</guid>
    <title>The main problem with Next.js 14 now</title>
    <link>https://www.nico.fyi/blog/the-problem-with-next-js-14</link>
    <description>
Eric Burel recently posted an article about [securing statically rendered paid content in Next.js with the App Router](https://www.ericburel.tech/blog/static-paid-content-app-router). This article raised concerns that [Next.js makes it easy for people to do the wrong thing](https://x.com/TkDodo/status/1765457210694414377?s=20).

![](/static/images/articles/next-easy-to-do-wrong-thing.png.webp)

However, Next.js does not recommend protecting your routes in a layout, as you can read on [the Authentication page in the Next.js documentation](https://nextjs.org/docs/app/building-your-application/authentication#authorization-using-server-components). It is recommended to use middleware for route protection. Yet, Sebastian Markbåge, formerly of the React core team and now at Vercel, tweeted that [middleware shouldn&#39;t be used for auth either](https://x.com/sebmarkbage/status/1765414733820129471?s=20), which adds to the confusion.

![](/static/images/articles/middleawre-not-for-auth.png.webp)
</description>
    <pubDate>Thu, 07 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/the-quality-that-delights-otp-auto-fill</guid>
    <title>The Quality That Delights: OTP auto fill input</title>
    <link>https://www.nico.fyi/blog/the-quality-that-delights-otp-auto-fill</link>
    <description>
I recently read a blog post about the [two types of quality](https://zenorocha.com/the-two-types-of-quality) from Japan&#39;s perspective: atarimae hinshitsu (当たり前品質), the obvious quality, and miryokuteki hinshitsu (魅力的品質), the appealing quality. The author, Zeno Rocha, founder of Resend, gave several examples of products that work versus products that inspire across different industries, mostly software-related.

While I agree that adding _something_ beyond the obvious functionalities could make users love the products or be inspired by them, **I&#39;d like to add that this _something_ doesn&#39;t always have to be something that looks good**. It can be something small but extremely helpful and useful.

One of the best examples of this is the [OTP](https://en.wikipedia.org/wiki/One-time_password) auto-fill feature in the iOS keyboard. This feature, added by Apple since iOS 15, might be the most publicly loved feature Apple has ever introduced. You might have seen many posts stating that whoever </description>
    <pubDate>Wed, 06 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/prisma-join-relation</guid>
    <title>The new join relation in Prisma</title>
    <link>https://www.nico.fyi/blog/prisma-join-relation</link>
    <description>
Recently, Prisma [released a highly requested feature: support for database-level joins](https://www.prisma.io/blog/prisma-orm-now-lets-you-choose-the-best-join-strategy-preview/?via=nicofyi). Previously, Prisma sent multiple queries to retrieve related data, then combined the results at the application level. However, with this recent release, Prisma now offers the option to choose between the new `join` strategy and the previous `query` strategy.

Currently, this feature is still behind a preview flag, so you need to explicitly enable it in the schema.prisma file:

```
generator client {
  provider        = &quot;prisma-client-js&quot;
  previewFeatures = [&quot;relationJoins&quot;]
}
```

Also, note that this feature currently only works with PostgreSQL and MySQL databases. Once you have enabled the feature, you must run `prisma generate` again. Then, you can specify which strategy to use when querying:

```typescript
const users = await prisma.user.findMany({
  relationLoadStrategy: &#39;join&#39;, // or &#39;qu</description>
    <pubDate>Tue, 05 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>prisma</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/use-sync-external-store-in-react</guid>
    <title>useSyncExternalStore is awesome</title>
    <link>https://www.nico.fyi/blog/use-sync-external-store-in-react</link>
    <description>
Recently, [someone asked on Twitter how to render the user&#39;s browser time in Next.js](https://twitter.com/chronark_/status/1762049915847376939), which uses React Server Components, without encountering hydration errors. While [Dan Abramov mentioned it&#39;s possible to suppress hydration mismatch errors](https://twitter.com/dan_abramov2/status/1762066351764406575) using the [`suppressHydrationWarning` prop](https://react.dev/reference/react-dom/client/hydrateRoot#suppressing-unavoidable-hydration-mismatch-errors), displaying the browser&#39;s time without this workaround is indeed feasible.

[My initial approach](https://twitter.com/2co_p/status/1762063508697448919) was to check for the `window` object on the server:

```javascript
if (typeof window === &#39;undefined&#39;) {
  return null
}

return &lt;&gt;{new Date().toISOString()}&lt;/&gt;
```

However, it was correctly noted that this still leads to hydration mismatches. This led me to utilize `useState` and `useEffect`:

```typescript
&#39;use client&#39;;

import </description>
    <pubDate>Mon, 04 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/the-locksmith-paradox</guid>
    <title>The Locksmith Paradox</title>
    <link>https://www.nico.fyi/blog/the-locksmith-paradox</link>
    <description>
In the world of work and craftsmanship, there&#39;s an intriguing phenomenon known as the _Locksmith&#39;s Paradox_. It tells the story of a locksmith who, as a beginner, takes an hour to fix a lock. He charges \$100 for his effort. His customers happily pay, valuing the visible effort and time he invests. Yet, as the locksmith grows more skilled, he can complete the same task in just fifteen minutes. Now, the same fee suddenly draws discontent. &quot;Why $100 for such quick work?&quot; they question. **This paradox shows a disconnect in how we see the value of work. This is especially true as expertise increases efficiency**.

But, why does this paradox occur? A compelling suspicion is that many people lack firsthand experience in the creative process or in mastering a skill. Thus, they struggle to appreciate the effort and time that goes into becoming efficient. They see only the end result—a task completed swiftly and with ease. **But, they don&#39;t see the years of practice, learning from mistakes, or</description>
    <pubDate>Fri, 01 Mar 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>philosophy</category><category>personal</category><category>thought</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/the-power-of-two-random-choices</guid>
    <title>The power of two random choices</title>
    <link>https://www.nico.fyi/blog/the-power-of-two-random-choices</link>
    <description>
I recently found a paper on Twitter. It&#39;s called &quot;[The Power of Two Random Choices: A Survey of Techniques and Results](http://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf).&quot; It explores the big boosts in load balancing that come from making two random choices instead of one.

![](/static/images/articles/balls-bin.jpeg.webp)

Consider the process of distributing balls into bins. **Placing each ball into the emptier of two random bins makes the distribution more balanced. This is better than placing each ball into a random bin**. This principle applies in theory and in real-world uses. These include hashing, shared memory, and low-congestion routing.

This technique enhances task distribution among servers. It offers a better alternative to random server selection for each new task. The logic behind this method is straightforward. Here&#39;s how it works:

1. **Random Selection**: Upon the arrival of a new task, the system randomly selects two servers instead of one.
2. **Co</description>
    <pubDate>Thu, 29 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>statistics</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/array-with-and-at-in-javascript</guid>
    <title>You should start using with and at methods in Javascript</title>
    <link>https://www.nico.fyi/blog/array-with-and-at-in-javascript</link>
    <description>
JavaScript has come a long way, introducing many new APIs that enable us to write less code. For example, I recently discovered two array methods named [with](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/with) and [at](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at). Suppose you want to create a new array from an existing one, where a certain element in the new array is replaced with a new value. Previously, you might have done something like this:

```javascript
const arr = [1, 2, 3, 4, 5]
const newArr = [...arr] // Copy the array
newArr[1] = 10 // Change the value of index 1
console.log(newArr) // [1, 10, 3, 4, 5]
```

However, with the `with` method, we can simply write:

```javascript
const newArr = arr.with(1, 10)
```

The `with` method also accepts a negative index, so you can easily change the last element in the array, like so:

```javascript
const newArr = arr.with(-1, 10) // [1, 2, 3, 4, 10]</description>
    <pubDate>Wed, 28 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>javascript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/sparse-array-in-javascript</guid>
    <title>Did you know about sparse array in Javascript?</title>
    <link>https://www.nico.fyi/blog/sparse-array-in-javascript</link>
    <description>
I stumbled upon something called a [sparse array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#sparse_arrays) in the MDN web docs. It&#39;s one of those things that makes JavaScript &quot;interesting&quot; 😂.

Sparse arrays are arrays with empty slots. However, these empty slots are not the same as slots filled with `undefined`. There are several ways to create sparse arrays:

```javascript
// Using the Array constructor:
const a = Array(5) // [ &lt;5 empty items&gt; ]

// Using consecutive commas in an array literal:
const b = [1, 2, , , 5] // [ 1, 2, &lt;2 empty items&gt;, 5 ]

// Directly setting a slot with an index greater than the array&#39;s length:
const c = [1, 2]
c[4] = 5 // [ 1, 2, &lt;2 empty items&gt;, 5 ]

// Elongating an array by directly setting its length:
const d = [1, 2]
d.length = 5 // [ 1, 2, &lt;3 empty items&gt; ]

// Deleting an element:
const e = [1, 2, 3, 4, 5]
delete e[2] // [ 1, 2, &lt;1 empty item&gt;, 4, 5 ]
```

Consider the following sparse array: `const arr = [</description>
    <pubDate>Tue, 27 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>javascript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/how-to-use-open-props-with-tailwind</guid>
    <title>How to use Open Props with Tailwind CSS</title>
    <link>https://www.nico.fyi/blog/how-to-use-open-props-with-tailwind</link>
    <description>
There&#39;s a new CSS tool called [Open Props](https://open-props.style), an open-source collection of CSS variables. It&#39;s essentially a collection of [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) that can be utilized anywhere CSS is used.

Since these are merely CSS variables, we can integrate them with [Tailwind CSS](https://tailwindcss.com). We don&#39;t even need to utilize everything Open Props offers. For instance, we can selectively use the Easing variables to enhance our CSS animations. As an example, I&#39;ve added the `ease-elastic-in-out-5` animation to the blog post titles when hovered over on this website. Try hovering over the blog title above.

## How to use

First, let&#39;s install Open Props:

```shell
npm install open-props
```

Then, we&#39;ll update our CSS file. For this website, I updated the [tailwind.css file](https://github.com/nicnocquee/nico.fyi/blob/main/css/tailwind.css):

```css
@import &#39;open-props/easings&#39;;
```

Next, we need</description>
    <pubDate>Mon, 26 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>tailwind</category><category>css</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/console-ninja</guid>
    <title>Console Ninja for VS Code</title>
    <link>https://www.nico.fyi/blog/console-ninja</link>
    <description>
I just found out about this neat extension for VS Code, [Console Ninja](https://console-ninja.com). It shows the `console.log` or breakpoint outputs inline or in a new panel in your VS Code.

![](/static/images/articles/console-ninja.png.webp)

---

Are you working in a team environment and your pull request process slows your team down? Then you have to grab a copy of my book, [Pull Request Best Practices](https://pr.nico.fyi)!

![](/static/images/articles/pull-request-best-practices.png)
</description>
    <pubDate>Fri, 09 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/dunning-kruger</guid>
    <title>The Dunning-Kruger Effect</title>
    <link>https://www.nico.fyi/blog/dunning-kruger</link>
    <description>
A phenomenon that&#39;s endlessly fascinating...

In psychology, a cognitive bias is intriguing. It captures why some people, despite having little knowledge, feel overly confident. Meanwhile, experts often underestimate their skills. This is known as the _Dunning-Kruger Effect_.

The Dunning-Kruger Effect has two key components:

- **Overconfidence in Novices**: People with limited understanding or skill in a domain tend to overvalue their competence. They don&#39;t recognize their own limitations, so they misjudge their abilities.
- **Experts are often humble**: Individuals with deep knowledge or skills often undervalue their expertise. They might assume that what comes easily to them is equally easy for others, leading to a skewed self-perception.

This cognitive bias highlights a paradox in self-assessment. It also shows the importance of seeking a balanced understanding of our own abilities.

Why does this happen? Simply put:

For beginners, ignorance of a field&#39;s vastness can make what </description>
    <pubDate>Thu, 08 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category><category>thought</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/postgres-json-path</guid>
    <title>PostgreSQL&#39;s jsonpath</title>
    <link>https://www.nico.fyi/blog/postgres-json-path</link>
    <description>
As a web and iOS developer, I often discover valuable features in PostgreSQL that can greatly improve our applications. One such feature is [jsonpath](https://www.postgresql.org/docs/16/datatype-json.html#DATATYPE-jsonpath), which allows us to query and manipulate JSON data directly in the database. PostgreSQL introduced jsonpath in version 12. It combines the flexibility of JSON with the strength of PostgreSQL.

### Getting Started with jsonpath

jsonpath is like XPath for XML but made for JSON. It has a query language that helps developers find specific elements in a JSON document stored in a PostgreSQL table. This ability is not just for getting data, but for doing it accurately and efficiently. It lets you easily perform complex queries on JSON data.

It uses a simple syntax, starting with the `$` symbol to represent the root of the JSON document. You can access specific properties by using the `$.name` syntax. If you have an array, like a list of users, you can access specific el</description>
    <pubDate>Wed, 07 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>postgres</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/postgres-money-type</guid>
    <title>PostgreSQL&#39;s Money Type</title>
    <link>https://www.nico.fyi/blog/postgres-money-type</link>
    <description>
When designing databases for applications that handle money transactions, choosing the right data type for currency values is crucial. PostgreSQL provides a specialized solution for this: the `money` type. Let&#39;s explore why the `money` type is a good option for managing currency values and when you should consider other alternatives.

## Understanding the Money Type

The `money` type in PostgreSQL stores currency amounts accurately and formats them according to the locale. Here&#39;s what the `money` type offers:

- **Fixed Decimal Precision**: Ideal for financial calculations that require accuracy. The `money` type ensures precise storage and calculation of currency values, avoiding issues with floating-point arithmetic.
- **Locale-aware Formatting**: This feature automatically adjusts the currency symbol and formatting based on the server&#39;s locale settings. It&#39;s great for applications serving a localized user base. However, it may cause inconsistencies when the application spans multipl</description>
    <pubDate>Tue, 06 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>postgres</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/react-ink-cli-chat-supabase</guid>
    <title>Make CLI app with React</title>
    <link>https://www.nico.fyi/blog/react-ink-cli-chat-supabase</link>
    <description>
React has been around for a while and has been adopted for many kinds of apps, not only web apps but also mobile native apps. But did you know that you can use React to make a command-line app? Using [ink](https://github.com/vadimdemedes/ink), we can use React to create interactive and sleek-looking command-line apps!

To give an example, I made a small demo CLI app. It&#39;s a real-time chat app using [Supabase&#39;s Realtime feature](https://supabase.com/docs/guides/realtime). Here is the main React component called `App`:

```typescript
export default function App({
  room,
  nickname,
  supabaseKey,
  supabaseUrl,
}: Props) {
  const supabase = createClient(supabaseUrl, supabaseKey);
  const channel = supabase.channel(room);

  const [userInput, setUserInput] = useState(&#39;&#39;);
  const [messageToSend, setMessageToSend] = useState(&#39;&#39;);
  const [isSending, setIsSending] = useState(false);

  useInput((input, key) =&gt; {
    if (key.return) {
      setMessageToSend(userInput);
      setUserInput(</description>
    <pubDate>Mon, 05 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category><category>react</category><category>cli</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/type-safe-next-js-routing</guid>
    <title>Type safe routing for Next.js</title>
    <link>https://www.nico.fyi/blog/type-safe-next-js-routing</link>
    <description>
I found a blog post that explains [how to make Next.js routing more type-safe](https://www.flightcontrol.dev/blog/fix-nextjs-routing-to-have-full-type-safety). It&#39;s an interesting concept, but when I tested the code, I encountered some issues. To address this, I created an npm package called [@nicnocquee/next-type-safe-routing](https://www.npmjs.com/package/@nicnocquee/next-type-safe-routing) and published it on npm.

The website also uses this library in certain areas, such as the [blog route](https://github.com/nicnocquee/nico.fyi/blob/main/app/blog/routes.ts). You can find the code for this route on the GitHub repo of [nicnocquee/nico.fyi](https://github.com/nicnocquee/nico.fyi).

---

Are you working in a team environment and your pull request process slows your team down? Then you have to grab a copy of my book, [Pull Request Best Practices](https://pr.nico.fyi)!

![](/static/images/articles/pull-request-best-practices.png)
</description>
    <pubDate>Fri, 02 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/typescript-identity-util-helper</guid>
    <title>Identity Helper Type in TypeScript</title>
    <link>https://www.nico.fyi/blog/typescript-identity-util-helper</link>
    <description>
Someone on Twitter asked what the following TypeScript custom generic actually does:

```typescript
type Identity&lt;T&gt; = T extends object ? { [K in keyof T]: T[K] } : T
```

Can you figure it out? Let me explain. This TypeScript type, `Identity&lt;T&gt;`, is a utility type that takes a generic type `T` and returns a type structurally identical to `T`. Essentially, it&#39;s a no-op for TypeScript&#39;s type system, meaning it doesn&#39;t modify the type in any significant way. Here&#39;s a breakdown:

- `T extends object ? { [K in keyof T]: T[K] } : T` uses a conditional type to check if `T` is an object type.
  - If `T` is an object (i.e., not a primitive type like `string`, `number`, or `boolean`), it maps over all keys of `T` (`keyof T`) and constructs a new type with the same keys and their corresponding types (`T[K]`). This process recreates the original type `T` without modification.
  - If `T` is not an object, it simply returns `T` as is.

So, what&#39;s its actual purpose? One application is to _unpack_ </description>
    <pubDate>Thu, 01 Feb 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/prisma-include-query-with-satisfies-typescript</guid>
    <title>Reuse include in Prisma Query with TypeScript satisfies</title>
    <link>https://www.nico.fyi/blog/prisma-include-query-with-satisfies-typescript</link>
    <description>
The `satisfies` operator in TypeScript [has been available since TypeScript 4.9](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator), but I hadn&#39;t had the chance to use it in my production websites until a few days ago, when I finally did.

I have the following [Prisma](https://www.prisma.io/?via=nicofyi) models:

```
model Participant {
  id                                       String            @id @default(uuid())
  createdAt                                DateTime          @default(now())
  participantGroup                         ParticipantGroup  @relation(fields: [participantGroupId], references: [id])
  participantGroupId                       String
}

model ParticipantGroup {
  id           String        @id @default(uuid())
  participants Participant[]
  course       Course[]

  @@map(&quot;participant_groups&quot;)
}
```

In one of my functions, I needed to:

1. Fetch a participant, including the `participantGroup`.
2. Perform some</description>
    <pubDate>Wed, 31 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/code-endure-beyond-tenure</guid>
    <title>Making Your Code Endure Beyond Your Tenure</title>
    <link>https://www.nico.fyi/blog/code-endure-beyond-tenure</link>
    <description>
Do you consider your code&#39;s fate when you leave a project? In the fast-paced world of software development, this critical aspect is often ignored. Think about the long-term impact of your work, not just solving immediate issues.

As a developer, you will eventually inherit a project. The existing code shows how the original developer thought. It shows how they faced challenges and solved problems. Your job is to understand and build upon this existing code.

&gt; Donald Knuth said, &quot;The best programs are written so that computing machines can perform them quickly and so that human beings can understand them clearly.&quot;

To have a maintainable codebase, you need **good documentation and solid testing**. The documentation should explain what the code does. It should also explain the thinking behind it, especially for complex parts. This helps future developers understand both the &#39;how&#39; and the &#39;why.&#39; Documentation includes not only the README files but also the inline comments in the source </description>
    <pubDate>Tue, 30 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/close-dialog-with-use-fetcher-remix</guid>
    <title>How to close a dialog on success or render an error with Remix fetchers</title>
    <link>https://www.nico.fyi/blog/close-dialog-with-use-fetcher-remix</link>
    <description>
[Ryan Florence of Remix tweeted](https://twitter.com/ryanflorence/status/1746255053357555930) about how to close a dialog on success or to display some error messages after submitting a form with Remix&#39;s [useFetcher](https://remix.run/docs/en/main/hooks/use-fetcher).

```typescript
export async function action({ request }: ActionFunctionArgs) {
  let formData = await request.formData();
  let status = formData.get(&#39;status&#39;);
  return status === &#39;success&#39;
    ? { ok: true, error: null }
    : { ok: false, error: &#39;There was a problem&#39; };
}

function Example() {
  let ref = useRef&lt;HTMLDialogElement&gt;(null);
  let fetcher = useFetcher&lt;typeof action&gt;();

  useEffect(() =&gt; {
    if (fetcher.state === &#39;idle&#39; &amp;&amp; fetcher.data?.ok) {
      ref.current?.close();
    }
  }, [fetcher]);

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; ref.current?.showModal()}&gt;Open Dialog&lt;/button&gt;
      &lt;dialog ref={ref}&gt;
        &lt;fetcher.Form method=&quot;post&quot;&gt;
          &lt;p&gt;{fetcher.state === &#39;idle&#39; ? &#39;Make a choice</description>
    <pubDate>Mon, 29 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>remix</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/basic-visitor-count-nextjs-vercel</guid>
    <title>Basic visitor count using Next.js middleware and Vercel KV</title>
    <link>https://www.nico.fyi/blog/basic-visitor-count-nextjs-vercel</link>
    <description>
Previously [I mentioned about `waitUntil` function in Next.js](/blog/wait-until-nextjs-vercel) and gave an example of updating inventory views in an e-commerce website. This time, I&#39;m sharing a real example of using `waitUntil` by adding a visitor count to this blog.

I&#39;m using [Vercel KV](https://vercel.com/docs/storage/vercel-kv) because it&#39;s free and relatively easy to integrate. First, go to your project in the Vercel dashboard, then navigate to the Storage tab and click &quot;Connect Store.&quot; You need to create a new KV Durable Redis storage. Follow the instructions until you get the environment variables needed to connect to it from your Next.js app.

![Vercel KV](/static/images/articles/vercel-kv.png.webp)

Once you have the environment variables, add the following code to your `middleware.ts`:

```typescript
import { kv } from &#39;@vercel/kv&#39;
import { NextFetchEvent, NextResponse } from &#39;next/server&#39;

export const config = {
  matcher: [
    /*
     * Match all request paths except for</description>
    <pubDate>Fri, 26 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/from-dishwashers-to-economics</guid>
    <title>Navigating Decision-Making: From Dishwashers to Economics</title>
    <link>https://www.nico.fyi/blog/from-dishwashers-to-economics</link>
    <description>
Have you ever pondered a simple decision, only to realize it&#39;s more complex than you thought? Let&#39;s explore decision-making with a mundane example that came to my mind from time to time.

![](/static/images/articles/dishwashers.png.webp)

It started with a dishwasher. Owning one in Switzerland was new to me, as I didn&#39;t have this convenience growing up in Indonesia or during my 7 years in Japan. Over time, I considered installing a second dishwasher in my kitchen. The reason was simple: it would keep the kitchen cleaner and save time. But then, a thought occurred: _if I had a million dollars_, would I still opt for two dishwashers? **Probably not**. I&#39;d likely hire a maid. This realization led me down a path exploring various economic laws and concepts.

## Law of Diminishing Returns

First up is the **Law of Diminishing Returns**. In economics, _it&#39;s the point where adding more of a production factor results in a smaller increase in output_. Applying this to the dishwasher scenario: </description>
    <pubDate>Thu, 25 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category><category>thought</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/wait-until-nextjs-vercel</guid>
    <title>Enhancing User Experience with Background Tasks in Vercel Edge Middleware</title>
    <link>https://www.nico.fyi/blog/wait-until-nextjs-vercel</link>
    <description>
In the fast-paced world of web development, optimizing user experience is key. One effective strategy is offloading non-critical tasks to run in the background. Vercel&#39;s Edge Middleware offers a powerful feature for this: the [waitUntil](https://nextjs.org/docs/app/building-your-application/routing/middleware#waituntil-and-nextfetchevent) function. `waitUntil` is a **function that allows certain tasks to be executed after sending the response to the user**. This means critical response time is not affected by tasks that can be completed later. Let’s dive into how it can be leveraged for background processing, using an online store&#39;s inventory update as an example.

Say we&#39;re running an online store where it&#39;s crucial to keep inventory counts accurate. However, updating the inventory each time a product is viewed shouldn&#39;t slow down the page load for the user. Here’s a simplified code snippet demonstrating this concept:

```typescript
import { NextResponse } from &#39;next/server&#39;

export </description>
    <pubDate>Wed, 24 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/occams-razor-programming</guid>
    <title>Slash Through Complexity using Occam&#39;s Razor</title>
    <link>https://www.nico.fyi/blog/occams-razor-programming</link>
    <description>
![Occam&#39;s Razor](/static/images/articles/ocamms-razor.png.webp)

Occam&#39;s Razor, a principle by William of Ockham, suggests that **the simplest solution is often correct**. This idea is relevant in today&#39;s tech landscape, especially in programming. But why?

## Simplicity is Key

At its core, Occam&#39;s Razor is about simplicity. In programming, this means choosing the simplest solution that gets the job done well. Simple code is easier to understand, maintain, and debug.

## Readability Over Complexity

A golden rule in coding is to write readable code. Why? Because **code is read more often than it&#39;s written**. Occam&#39;s Razor pushes us to favor straightforward solutions. This means prioritizing clarity over cleverness. Use clear function declarations instead of convoluted expressions.

## Avoiding Over-Engineering

The razor also warns against over-engineering. It&#39;s tempting to create a complex system for a simple problem. Occam&#39;s Razor reminds us to stay grounded. We shouldn&#39;t get carri</description>
    <pubDate>Tue, 23 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/cron-job-with-postgres</guid>
    <title>Cron job in PostgreSQL</title>
    <link>https://www.nico.fyi/blog/cron-job-with-postgres</link>
    <description>
![Cron job anime style according to ChatGPT](/static/images/articles/cronjob.png.webp)

In the world of database management, automating routine tasks is not just a convenience—it&#39;s a necessity. For PostgreSQL users, [`pg_cron`](https://github.com/citusdata/pg_cron) is a powerful tool that allows the scheduling of database jobs directly from the database itself. It&#39;s incredibly useful for routine maintenance tasks like data aggregation, cleanup operations, or even triggering complex workflows.

Let&#39;s say you want to periodically update `status` of `projects` whose `updatedAt` values are older than certain days. To use pg_cron for this, you&#39;ll need to schedule a cron job that executes an SQL update query:

1. **Install pg_cron**: Make sure pg_cron is installed on your PostgreSQL server. If it&#39;s not installed, you can find instructions on the [pg_cron](https://github.com/citusdata/pg_cron) GitHub page.
2. **Enable pg_cron**: Modify your PostgreSQL config file (usually postgresql.conf) to</description>
    <pubDate>Mon, 22 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>database</category><category>postgres</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/galls-law-programming</guid>
    <title>Gall&#39;s Law: Unraveling the Secrets to Successful Programming Projects</title>
    <link>https://www.nico.fyi/blog/galls-law-programming</link>
    <description>
&gt; A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system.

In the dynamic world of software development, a hidden gem often goes unnoticed. It can make or break your project&#39;s success. It&#39;s called **Gall&#39;s Law**. This principle seems counterintuitive at first but holds profound wisdom. Gall&#39;s Law states that complex systems that work invariably evolve from simpler systems that worked. The key takeaway? **If you want to build a system that works, start simple**.

![Gall&#39;s Law according to ChatGPT](/static/images/articles/galls-law.png.webp)

As programmers, we often fall into the trap of overengineering. We dream big. We envision a system so comprehensive and feature-rich that it covers every conceivable need. But here&#39;s the catch: **Complexity is the enemy of execution**.

Let&#39;s take a journey throug</description>
    <pubDate>Fri, 19 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/check-file-remote-using-range-header</guid>
    <title>Checking File Existence Efficiently Using Range Requests</title>
    <link>https://www.nico.fyi/blog/check-file-remote-using-range-header</link>
    <description>
In the modern web, where resources and data are abundant, efficiency is key. One particularly neat trick that web developers and system administrators might find useful is checking the existence of a file on a server without downloading the entire file. This is especially handy for large files, where downloading the whole thing just to check its existence is impractical. The solution? **HTTP range requests**.

Range requests are a feature of the HTTP protocol that _allow users to request a part of a file from a server_. This is done using the `Range` HTTP header. For example, requesting the first 500 bytes of a file can be done by setting the header to `Range: bytes=0-499`.

When you only need to confirm whether a file exists, downloading the entire file is like using a sledgehammer to crack a nut – overkill. Range requests allow you to request just a small part of the file. This way, you&#39;re not wasting bandwidth or time downloading unnecessary data.

The `curl` command-line tool can </description>
    <pubDate>Thu, 18 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/power-of-collaboration</guid>
    <title>Stop torturing yourself working alone. Talk with others.</title>
    <link>https://www.nico.fyi/blog/power-of-collaboration</link>
    <description>
There&#39;s this thing I&#39;ve experienced **so many times**, and maybe you can relate. More often than not, I&#39;ve found myself wrestling with a coding problem, only to have it untangle itself almost magically when I discuss it with someone else.

![Rubber duck debugging](/static/images/articles/power-of-collaboration.png.webp)

Have you heard of **&quot;Rubber Duck Debugging&quot;** method? It&#39;s a humorous, yet surprisingly effective technique where you explain your code line by line to an inanimate object – like a rubber duck. The act of verbalizing the problem often leads to a eureka moment. It&#39;s not the duck that&#39;s doing the magic, though (sorry, duck fans). It&#39;s the process of stepping back, articulating the issue, and viewing it from a different angle that does the trick.

Imagine you&#39;re working on a complex function in React and you&#39;re stuck. You grab a rubber duck (or a coffee mug, no judgment here) and start explaining your use of useState and useEffect. Mid-sentence, you realize, &quot;Oh, my depe</description>
    <pubDate>Wed, 17 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/dangerously-set-inner-html</guid>
    <title>Understanding dangerouslySetInnerHTML in React: Use Cases and Risks</title>
    <link>https://www.nico.fyi/blog/dangerously-set-inner-html</link>
    <description>
In the world of React development, handling HTML content requires a careful approach, particularly when it comes to injecting raw HTML into the DOM. One of React&#39;s features, `dangerouslySetInnerHTML`, often becomes a topic of discussion due to its potential risks and specific use cases. In this blog post, we&#39;ll explore what `dangerouslySetInnerHTML` is, why it&#39;s considered dangerous, and the scenarios where its use might be acceptable.

## What is `dangerouslySetInnerHTML`?

React&#39;s `dangerouslySetInnerHTML` is a property that allows developers to set HTML content directly into the DOM from within a React component. It&#39;s analogous to using `innerHTML` in plain JavaScript but comes with an explicit warning in its name, indicating the potential risks associated with its use.

## Why is `dangerouslySetInnerHTML` Dangerous?

The primary risk associated with `dangerouslySetInnerHTML` lies in its ability to bypass React&#39;s built-in [Cross-Site Scripting (XSS)](https://owasp.org/www-community</description>
    <pubDate>Tue, 16 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/chesterton-fence-programming</guid>
    <title>How Understanding the Chesterton Fence Can Make You a Better Programmer!</title>
    <link>https://www.nico.fyi/blog/chesterton-fence-programming</link>
    <description>
![Gilbert Keith Chesterton](https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Gilbert_Chesterton.jpg/440px-Gilbert_Chesterton.jpg)

As programmers, we often find ourselves eager to refactor, rewrite, or remove code that seems outdated or unnecessary. However, before we leap into action, it&#39;s essential to consider the wisdom of G.K. Chesterton&#39;s famous principle: the **Chesterton Fence**. This principle, though originating from a completely different context, holds invaluable lessons for developers, especially in the complex world of software engineering.

Imagine stumbling across a fence in the middle of a software project, much like Chesterton&#39;s analogy. This fence could be an obscure piece of code, a puzzling function, or even a deprecated library. Your first instinct, driven by a desire for efficiency and modernization, might be to remove it. But here lies the crux of Chesterton&#39;s lesson: **&quot;Do not remove a fence until you know why it was put up in the first place.&quot;**

In t</description>
    <pubDate>Mon, 15 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/eval-global-and-local</guid>
    <title>Understanding the Subtleties of JavaScript&#39;s eval: Local vs. Global Execution</title>
    <link>https://www.nico.fyi/blog/eval-global-and-local</link>
    <description>
JavaScript is a language full of nuances and subtleties, and one of the most intriguing aspects involves the `eval` function. A particularly interesting use case is the expression `(1, eval)(&#39;1+1&#39;)`, which not only demonstrates the flexibility of JavaScript but also sheds light on the difference between executing code in local versus global scopes. Let&#39;s dive in!

**The Syntax `(1, eval)(&#39;1+1&#39;)`**

At first glance, `(1, eval)(&#39;1+1&#39;)` might look like a cryptic, almost esoteric JavaScript expression. However, it&#39;s a brilliant showcase of the language&#39;s capabilities. This expression involves two main concepts: the comma operator and indirect `eval` execution.

1. **Comma Operator in JavaScript**: The comma operator `,` evaluates each operand from left to right and returns the value of the last operand. In `(1, eval)`, `1` is evaluated first (and does nothing substantial), followed by `eval`, resulting in the entire expression evaluating to `eval` itself.

2. **Indirect `eval` Execution**</description>
    <pubDate>Fri, 12 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/parkinsons-law-programming</guid>
    <title>How You&#39;re Falling for Parkinson&#39;s Law Without Even Realizing</title>
    <link>https://www.nico.fyi/blog/parkinsons-law-programming</link>
    <description>
![Cyril Northcote Parkinson](https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Cyril_Northcote_Parkinson_1961.jpg/440px-Cyril_Northcote_Parkinson_1961.jpg)

Parkinson&#39;s Law, a concept often quoted in management circles, states, **&quot;Work expands to fill the time available for its completion.&quot;** I&#39;ve seen firsthand how this principle plays out in the world of programming. It manifests in a fascinating way. When a development team is given a generous timeline for a project, the scope and complexity of the work often inflate to fill that timeline. This isn&#39;t necessarily due to laziness or inefficiency; rather, it&#39;s a byproduct of certain inherent tendencies in software development.

Developers, given ample time, might fall into the trap of perfectionism. The pursuit of the perfect code, architecture, or design can lead to endless refinements. _While striving for excellence is commendable, it often leads to diminishing returns_. Furthermore, a longer timeline can inadvertently encou</description>
    <pubDate>Thu, 11 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/node-async-local-storage</guid>
    <title>Async Local Storage in Node.js</title>
    <link>https://www.nico.fyi/blog/node-async-local-storage</link>
    <description>
Recently, a developer&#39;s tweet sparked a debate about Next.js&#39;s approach to simplifying its API, particularly regarding the [headers](https://nextjs.org/docs/app/api-reference/functions/headers#headers) and [cookies](https://nextjs.org/docs/app/api-reference/functions/cookies) functions. This tweet resonated with a significant number of developers who favored having the request object universally accessible.

Personally, I find myself agreeing with these viewpoints to some extent. However, the convenience offered by these functions cannot be overlooked. This led me to delve into how the `headers` and `cookies` functions are actually implemented in Next.js. Thanks to the open-source nature of the [Next.js repository](https://github.com/vercel/next.js), I was able to locate the [implementation of the headers function](https://github.com/vercel/next.js/blob/cfb75b77ea16a02bcc70ffe2e0f560701b814d7a/packages/next/src/client/components/headers.ts#L12). The key here seems to be the use of [As</description>
    <pubDate>Wed, 10 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/async-must-be-awaited</guid>
    <title>What is &quot;async&quot; must be await-ed</title>
    <link>https://www.nico.fyi/blog/async-must-be-awaited</link>
    <description>
Several months back, I experienced a sudden crash in [my pull request on the Monika project](https://github.com/hyperjumptech/monika/pull/1165), which caught my team off guard during the PR review, as noted in this [issue comment](https://github.com/hyperjumptech/monika/pull/1165#issuecomment-1803105597).

I was surprised because I expected any errors to be intercepted by the try-catch blocks already in place, which should have prevented the crash. However, **the error slipped through and crashed the app!**

After extensive debugging, I realized the root cause: the error was originating from an `async` function that wasn&#39;t invoked with `await`. Here&#39;s an illustration:

```typescript
const main = async () =&gt; {
  try {
    doSomething() // the bug
  } catch (error) {
    console.log(&#39;here?&#39;)
  }
}

const doSomething = async () =&gt; {
  throw new Error(`Error from doSomething`)
}

main()
```

The issue arose because the `doSomething` function, even though it throws an error, was mistakenly</description>
    <pubDate>Tue, 09 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/react-hydration-error</guid>
    <title>Another day, another React Hydration error</title>
    <link>https://www.nico.fyi/blog/react-hydration-error</link>
    <description>
Last week in one of our projects in Hyperjump, we discovered a [React hydration error](https://nextjs.org/docs/messages/react-hydration-error) in one of our pull request changes. It happened in the registration page where one of our developers added a select input that displays a list of timezones. Before I jumped into what caused the error and how we fixed it, let&#39;s talk about hydration error first.

## Wat de hek is Hydration Error?

Imagine you&#39;ve built a really cool LEGO castle. You carefully followed the instructions, placing each LEGO brick exactly where it&#39;s supposed to go. Now, your friend comes along and tries to add more LEGO bricks to your castle, but they don&#39;t follow the instructions. They put bricks in the wrong places or use the wrong pieces. The castle starts to look different than what the instructions show. That&#39;s sort of like a hydration error in React.

When you use server-side rendering in React, your app is like the LEGO castle. The server builds the initial webp</description>
    <pubDate>Mon, 08 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>pull request</category><category>react</category><category>next.js</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/next-js-app-static-by-default</guid>
    <title>Must remember when using Next.js App Router</title>
    <link>https://www.nico.fyi/blog/next-js-app-static-by-default</link>
    <description>
Here&#39;s one thing you must always remember when making website with Next.js with [App Router](https://nextjs.org/docs/app). By default, every page component undergoes **static rendering**, which means HTML files are generated at build time. Take a look at this example to grasp the importance of this detail.

```typescript
// app/support/page.tsx
export default async function SupportPage() {
    const now = new Date()
    return (
        &lt;div&gt;
            &lt;form action=&quot;/api/support&quot; method=&quot;POST&quot;&gt;
                &lt;input type=&quot;text&quot; name=&quot;comment&quot; &gt;
                &lt;input type=&quot;hidden&quot; name=&quot;date&quot; value={now} /&gt;
                &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
            &lt;/form&gt;
        &lt;/div&gt;
    )
}
```

This code might seem fine, but there&#39;s a hidden issue. Did you spot it? The `SupportPage` component, lacking any [dynamic functions](https://nextjs.org/docs/app/building-your-application/rendering/server-components#dynamic-functions) or a [`dynamic` constant set to `force-dynami</description>
    <pubDate>Thu, 04 Jan 2024 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>open source</category><category>nextjs</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/reduce-video-size-with-ffmpeg</guid>
    <title>Reduce video size with ffmpeg</title>
    <link>https://www.nico.fyi/blog/reduce-video-size-with-ffmpeg</link>
    <description>
Recently there&#39;s some people on [Twitter](https://twitter.com/2co_p) sharing a free Mac app that can compress video significantly while maintaining quality. After giving away my e-mail address, I downloaded the app and ran it. First thing I saw was a window asking me to enter the path for [ffmpeg](https://ffmpeg.org). Guess it&#39;s using ffmpeg under the hood.

So I asked [ChatGPT](https://chat.openai.com) for an ffmpeg command to reduce video size while maintaining the quality. It gave me this:

```shell
ffmpeg -i input.mp4 -vcodec libx264 -crf 37 output.mp4
```

I can set the `crf` value between 1 - 51 to adjust the quality of the video where higher number means worse quality. And turned out it&#39;s correct. I could reduce a 17 MB MP4 video down to 1.9 MB or less depends on the `crf` value.

Afterwards, I could add that command to [Forklift](https://binarynights.com) so that I don&#39;t need to open terminal to compress video files.

```shell
#!/bin/sh

/opt/homebrew/bin/ffmpeg -i $SOURCE_SEL</description>
    <pubDate>Thu, 21 Dec 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>mac</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/next-secure-download-file-update</guid>
    <title>Updated Next Secure Download to Next.js 14 with App Router</title>
    <link>https://www.nico.fyi/blog/next-secure-download-file-update</link>
    <description>
![next secure download icon](https://nextsecure.nico.fyi/_next/image?url=%2Fnext-secure-download-icon.webp&amp;w=640&amp;q=75)

I&#39;ve just rolled out a minor update for my open source project, [Next Secure Download](https://github.com/nicnocquee/next-secure-download), now fully embracing the power of Next.js 14! Here’s what’s new:

1. **App Router Migration**: I&#39;ve moved from the classic Pages router to the more dynamic [App Router](https://nextjs.org/docs/app/building-your-application/routing), enhancing the project&#39;s flexibility and scalability.
2. **Brand New Icon**: Check out the sleek, new project icon! It&#39;s the fresh face of Next Secure Download.
3. **Dedicated Sample Page**: Experience the improvements first-hand on our sample page - now hosted on my personal domain: [https://nextsecure.nico.fyi](https://nextsecure.nico.fyi).
4. **Enhanced Security**: I&#39;ve beefed up the security measures, especially when serving secret files. Peek at the [updated code](https://github.com/nicnocquee/next</description>
    <pubDate>Wed, 22 Nov 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>open source</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/pull-request-best-practices-book-black-friday-discount</guid>
    <title>Don&#39;t Miss Out: 60% Off &#39;Pull Request Best Practices&#39; This Black Friday!</title>
    <link>https://www.nico.fyi/blog/pull-request-best-practices-book-black-friday-discount</link>
    <description>
![Pull Request Best Practices](/static/images/articles/pull-request-best-practices-black-friday.png)

As the season of savings approaches, I&#39;m excited to announce a special Black Friday deal for my book, [&quot;Pull Request Best Practices.&quot;](https://pr.nico.fyi) This is an opportunity for developers and teams to elevate their skills in managing and evaluating pull requests - at an unbeatable price!

# Why This Book?

&quot;Pull Request Best Practices&quot; is more than just a guide; it&#39;s a toolkit for transforming your approach to code reviews. It&#39;s packed with insights, strategies, and real-life examples, tailored for developers looking to streamline their review processes and enhance code quality. Whether you&#39;re a seasoned developer or just starting out, this book is a valuable addition to your professional library.

# Black Friday Special Offer

We&#39;re offering [an incredible 60% discount on &quot;Pull Request Best Practices&quot;](https://nicopr.gumroad.com/l/pr-best-practices/black-friday-23) - a deal tha</description>
    <pubDate>Mon, 20 Nov 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category><category>book</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/next-secure-download-file-free</guid>
    <title>Serve a password-protected file for FREE</title>
    <link>https://www.nico.fyi/blog/next-secure-download-file-free</link>
    <description>
Few years ago I made this little project to solve my own problem. I needed a solution to make a file publicly downloadable, but

|     |                                                          |
| --- | -------------------------------------------------------- |
| ✅  | it should be FREE.                                       |
| ✅  | the files are password-protected.                        |
| ✅  | the files can be directly downloaded, no download page.  |
| ✅  | the files can be downloaded from script or command line. |

I couldn&#39;t find anything that meets those requirements. So I made this project that allows me to store the files in private GitHub repository and serving them through Vercel, both of which are FREE.

Today I updated it to Next.js 14.

---

By the way, [I&#39;m making a book about Pull Requests Best Practices. Check it out](https://pr.nico.fyi)!
</description>
    <pubDate>Wed, 15 Nov 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>open source</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/pull-request-best-practices-book</guid>
    <title>Introducing &quot;Pull Request Best Practices&quot;</title>
    <link>https://www.nico.fyi/blog/pull-request-best-practices-book</link>
    <description>
![Pull Request Best Practices](/static/images/articles/pull-request-best-practices.png)

I&#39;m excited to share a project that&#39;s close to my heart: my new book, &quot;Pull Request Best Practices.&quot; Designed for developers navigating the complexities of team coding, this book is a deep dive into the world of Pull Requests.

Drawing from my extensive experience in diverse coding environments, I&#39;ve compiled insights and strategies to enhance team dynamics and code quality. This book is a must-read for anyone looking to refine their approach to PRs, whether you&#39;re leading a team or contributing as a key player.

The focus of &quot;Pull Request Best Practices&quot; is to provide a comprehensive guide to streamline your review process, promote effective collaboration, and maintain high standards of code integrity. It&#39;s a tool to help you and your team achieve coding excellence.

As an introductory offer, the book is available for preorder with a significant discount. This is an opportunity to elevate your co</description>
    <pubDate>Tue, 14 Nov 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category><category>book</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/react-server-actions-returns-jsx</guid>
    <title>How to return JSX from React Server Actions</title>
    <link>https://www.nico.fyi/blog/react-server-actions-returns-jsx</link>
    <description>
Welcome to the cutting-edge world of web development, where Next.js continues to push the boundaries! Let&#39;s dive into one of its most exciting features: [Server Actions](https://nextjs.org/docs/app/api-reference/functions/server-actions). This innovative tool isn&#39;t just another addition to the React ecosystem; it&#39;s a paradigm shift exclusively available in Next.js (for now).

Imagine this: you&#39;re working on a project, and you need to mutate data on the server. Traditionally, this would mean defining and writing an API endpoint – a process that can be time-consuming and, let&#39;s be honest, a bit tedious. Enter Server Actions. This feature streamlines the whole process, enabling you to perform data mutations directly on the server, no extra API endpoints needed. It&#39;s all about efficiency and simplicity, making your development workflow smoother and faster.

But here&#39;s a twist that&#39;s not in the official documentation, something discovered by the innovative minds in our developer community.</description>
    <pubDate>Sat, 11 Nov 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>pull request</category><category>react</category><category>next.js</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/pr-review-reduce-state</guid>
    <title>Use single state instead of three</title>
    <link>https://www.nico.fyi/blog/pr-review-reduce-state</link>
    <description>
Today I received a pull request, like the following

```typescript
const SomeComponent = () =&gt; {
  const [isStatusPageModalOpen, setIsStatusPageModalOpen] = useState(false)
  const [isEditProbeModalOpen, setIsEditProbeModalOpen] = useState(false)
  const [isDeleteProbeModalOpen, setIsDeleteProbeModalOpen] = useState(false)

  return (
    &lt;div&gt;
      {isStatusPageModalOpen ? &lt;StatusPageModal /&gt; : null}
      {isEditProbeModalOpen ? &lt;EditProbeModal /&gt; : null}
      {isDeleteProbeModalOpen ? &lt;DeleteProbeModal /&gt; : null}
    &lt;/div&gt;
  )
}
```

The problem with the code is that there can only be one modal appear at the same time. So using three states for this is a waste and could cause unintentional bug where two or three modals appear at the same time.

Instead, it&#39;s better to use a single state like the following

```typescript
type ModalOpenType = &#39;edit&#39; | &#39;delete&#39; | &#39;status&#39;
const [openedModal, setOpenedModal] = useState&lt;ModalOpenType | null&gt;(null)

return (
  &lt;div&gt;
    {openedModal =</description>
    <pubDate>Tue, 05 Sep 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>pull request</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/pr-review-link-instead-of-push</guid>
    <title>Use &lt;Link&gt; instead of router.push</title>
    <link>https://www.nico.fyi/blog/pr-review-link-instead-of-push</link>
    <description>
A PR was made like the following

```typescript
import { useRouter } from &#39;next/router&#39;;

const SomeComponent = () =&gt; {
  const router = useRouter()
  return (
    &lt;a onClick={() =&gt; {
      router.push(`/link/to/another/page`)
    }&gt;Go to somewhere&lt;/&gt;
  )
}
```

Few problems with that code:

1. When navigating to another page, use `&lt;Link&gt;` in Next.js.
2. When you don&#39;t need to perform anything else when the link is clicked, just use `href` prop. No need for `onClick` anymore.
</description>
    <pubDate>Mon, 04 Sep 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>pull request</category><category>react</category><category>next.js</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/discriminated-union</guid>
    <title>What is Discriminated Union in TypeScript?</title>
    <link>https://www.nico.fyi/blog/discriminated-union</link>
    <description>
Person:

- what does &#39;discriminated union&#39; mean?

ChatGPT:

- A &quot;discriminated union&quot; (also known as a &quot;tagged union&quot;, &quot;variant&quot;, &quot;sum type&quot;, or &quot;union type&quot; in various programming languages) is a data structure used to store values that could be of several different types. Only one of the types can be in use at any one time. It&#39;s a way to safely handle values of different types in a single, unified type.

The &quot;discriminated&quot; part comes from the fact that there&#39;s typically a &quot;tag&quot; or &quot;discriminator&quot; that indicates which type of value is currently stored in the union. This tag allows the program to safely interpret the stored value.

Here&#39;s a simple example in TypeScript, which supports discriminated unions:

```typescript
type Shape =
  | { kind: &#39;circle&#39;; radius: number }
  | { kind: &#39;rectangle&#39;; width: number; height: number }
  | { kind: &#39;square&#39;; sideLength: number }

function area(shape: Shape): number {
  switch (shape.kind) {
    case &#39;circle&#39;:
      return Math.PI * shape.radi</description>
    <pubDate>Sun, 03 Sep 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>pull request</category><category>react</category><category>next.js</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/pr-review-early-return</guid>
    <title>Early return for unhappy path</title>
    <link>https://www.nico.fyi/blog/pr-review-early-return</link>
    <description>
A PR was made, something like this:

```typescript
// next.js getServerSideProps
export const getServerSideProps = async (ctx) =&gt; {
  const { valid, event } = await isValidEvent({
    organizationSlug,
    projectSlug,
    probeSlug,
    eventSlug,
    userID: ctx.user?.id,
  })

  const currentEvent = {
    id: event?.id,
    locationId: event?.locationId,
    monika: event?.monikaId,
    alert: event?.alertId,
    response: parseEventResponse(event?.response),
    createdAt: event?.createdAt.toString(),
    recoveredAt: event?.recoveredAt?.toString() || &#39;&#39;,
    location: event?.location,
  }

  return {
    props: {
      currentEvent,
    },
  }
}
```

The problem with that PR is that it&#39;s reading the optional `event` too many times. Furthermore, in this case since it&#39;s [Next.js&#39;s getServerSideProps](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props), it&#39;s better to check the existence of `event` and return [notFound](https://nextjs.org/doc</description>
    <pubDate>Mon, 21 Aug 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>pull request</category><category>react</category><category>next.js</category><category>typescript</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/pr-review-tailwind-arbitrary-variant</guid>
    <title>Use Tailwind&#39;s Arbitrary Variant</title>
    <link>https://www.nico.fyi/blog/pr-review-tailwind-arbitrary-variant</link>
    <description>
A PR was made, something like this:

```typescript
const data = [
   {
    question: &#39;How are you?&#39;,
    answer: `I&#39;m good because:&lt;br/&gt;
    &lt;ul style=&quot;padding-left:20px;list-style-type: disc;&quot;&gt;
      &lt;li&gt;Something something bla bla bla&lt;/li&gt;
    &lt;/ul&gt;`
  },
  {
    question: &#39;Where are you from?&#39;,
    answer: `I&#39;m good because:&lt;br/&gt;
    &lt;ul style=&quot;padding-left:20px;list-style-type: disc;&quot;&gt;
      &lt;li&gt;Something something&lt;/li&gt;
    &lt;/ul&gt;`
  }
]

return (
  &lt;div&gt;
    {data.map((d, i) =&gt; {
      return (
        &lt;div key={i} dangerouslySetInnerHTML={{__html: d}} /&gt;
      )
    }}
  &lt;/div&gt;
)
```

The problem in that PR is that the data items are polluted by the `style`. It makes it harder for developers to read the data and not to mention the repetition. A better way is to keep the styling out of the data. Instead, let the parent component styles the HTML elements that appear in the data (`ul` element in this example) by using [Tailwind&#39;s arbitrary variants](https://tailwindcss.com/docs/hove</description>
    <pubDate>Wed, 09 Aug 2023 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>pull request</category><category>react</category><category>next.js</category><category>typescript</category><category>tailwind</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/coparrot-mock-server-without-installation</guid>
    <title>Coparrot - Mock server without installation and without configuration</title>
    <link>https://www.nico.fyi/blog/coparrot-mock-server-without-installation</link>
    <description>
![coparrot-dev](/static/images/articles/coparrot-03.jpg)

If you missed it, today I finally released this project called [Coparrot](https://coparrot.dev), a no-install and no-config mock server for mobile and front-end web developers (and probably designers too!). Check it out here

The idea came from an open source project from Hyperjump called [HTTPTarget](https://github.com/hyperjumptech/httptarget). My colleague made it to help in one of our projects. But I had some trouble installing and running it due to not knowing Go.

My frustration bore fruit to the idea of Coparrot. I wanted a mock server that I didn&#39;t need to install or configure. Then I also thought, when I developed a web page or an app screen that needs data from a server, I know exactly what kind of response I want from the server.

So what if I can just specify what response I want when sending a request to the server, and that server will return exactly that response. I wanted an echo server. And you know what animal</description>
    <pubDate>Tue, 18 Jan 2022 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>project</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/track-github-clones-views-traffic</guid>
    <title>How to track the traffic of views and clones of Github Repository</title>
    <link>https://www.nico.fyi/blog/track-github-clones-views-traffic</link>
    <description>
![](/static/images/articles/statistics.jpg)

# Problem

I wanted, actually needed, to track the number of times some of our repositories in Github got cloned by others. Fortunately, Github provides the clones traffic data from the repository&#39;s Insights. If you are the owner of the repository, or you are an administrator of the github organization, you&#39;d find the &quot;Traffic&quot; link in the sidebar of Insights page.

![](/static/images/articles/github-clones-views.png)

Unfortunately, this page only shows the data of the last two weeks and it cannot be exported. Another problem is that there are few repositories that I need to track. Opening the repository pages one by one is such a hassle.

# Solution

So I made a small command line interface (CLI) script, called [export-github-clones-views](https://github.com/hyperjumptech/export-github-clones-views), to fetch the traffic of the git clones and views from multiple repositories then optionally store them in a mongo database. You can read the</description>
    <pubDate>Mon, 07 Dec 2020 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/using-non-apple-mechanical-keyboard-on-mac-like-a-pro</guid>
    <title>Using Non-Apple Mechanical keyboard on Mac Like a Pro</title>
    <link>https://www.nico.fyi/blog/using-non-apple-mechanical-keyboard-on-mac-like-a-pro</link>
    <description>
![animal-crossing-keycaps](https://cdn.shopify.com/s/files/1/1679/2319/products/Infinikey-PBT-Islander_deskmat_Island-Hopper_025a79f4-a0e6-4ed1-a209-ed8848ebe3a3_720x.png?v=1589464642)

I&#39;ve been using the black Apple keyboard for a long time at home. But recently I was attracted by this gorgeous [Animal Crossing-theme keycaps](https://thekey.company/collections/islander/products/infinikey-pbt-islander) for mechanical keyboard and I decided to try using a mechanical keyboard.

## Finding The Keyboard ...

There are many mechanical keyboards out there. Finding one that I like wasn&#39;t that easy. I wanted a keyboard that is not so big. I don&#39;t need the number keypads. But not too compact that it doesn&#39;t have the arrow keys. Having a backlit is a plus.

Usually, the keyboard that has arrow keys is slightly wider than what I want. But I found one that has arrow keys but still compact, [Ducky Miya Pro Panda Edition keyboard](https://www.digitec.ch/en/s1/product/ducky-miya-pro-panda-edition-d</description>
    <pubDate>Tue, 19 May 2020 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/dev-note-env-variables-nextjs-safely-and-conveniently</guid>
    <title>[Dev Note] Using Environment Variables in NextJS Safely and Conveniently</title>
    <link>https://www.nico.fyi/blog/dev-note-env-variables-nextjs-safely-and-conveniently</link>
    <description>
---

**UPDATE May 11, 2020**

As of version 9.4, [NextJS finally has proper environment variables support](https://nextjs.org/blog/next-9-4#new-environment-variables-support). Simply put the variables in `.env` file:

- Environment variables are only available in the Node.js environment by default.
- Environment variables prefixed with NEXT*PUBLIC* are exposed to the browser.

---

# Problem

I really like [nextjs](https://nextjs.org/). But their recommendation to [add environment variable to the Javascript bundle](https://nextjs.org/docs/api-reference/next.config.js/environment-variables) is not appropriate in my opinion. According to the documentation, you need to add the environment variables in the `next.config.js` file.

```javascript
module.exports = {
  env: {
    customKey: &#39;my-value&#39;,
  },
}
```

However, this next.config.js file is usually commited to the project&#39;s repository. So if some of the environment variables are sensitive information (e.g., API keys), that informatio</description>
    <pubDate>Tue, 05 May 2020 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>nextjs</category><category>dev</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/dev-note-how-to-write-tests-for-react-comfortably</guid>
    <title>[Dev Note] How to Write Tests for React App Comfortably</title>
    <link>https://www.nico.fyi/blog/dev-note-how-to-write-tests-for-react-comfortably</link>
    <description>
# Why write tests

As a software developer, it is always exciting to see the code I wrote runs on the desktop browser or on the iPhone/android devices. The excitement (and the deadline) often makes me forget that there are many use cases that could happen when a user uses my app. A single screen/page could have many different use cases. For example, in a login page/screen, user could enter an invalid email address, the username and password doesn&#39;t match those in the server, a loading indicator could be visible when submitting the login credentials, and so on. We could, of course, test all these use cases manually ourselves. But it wouldn&#39;t scale as the app grows bigger. Because remember, we have to test all the cases every time we release a new update to the users.

![nobody-got-time-to-test-manually](/static/images/articles/aintnobody.gif)

# Why some don&#39;t write tests

I admit that writing tests is sometimes vexatious. It takes time to write tests since most of the time you will en</description>
    <pubDate>Fri, 17 Apr 2020 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>test</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/the-manual-a-philosoper-guide-to-life-epictetus</guid>
    <title>The Manual - A Philosopher&#39;s Guide to Life</title>
    <link>https://www.nico.fyi/blog/the-manual-a-philosoper-guide-to-life-epictetus</link>
    <description>
![The Manual A Philosopher&#39;s Guide to Life Epictetus](/static/images/articles/the-manual.jpg)

Sam Torode wrote this book as his own interpretation of the wisdom of Epictetus and Marcus Aurelius. The book is so short that I could finish it in one seating. But it has some interesting lessons about how to think of and to deal with the unfavorable things that are coming at us.

# There&#39;s a price for that

I used to (and probably will still) be envy of people who _made it big_ in life. I knew they paid the price for their successes with their money, health, energy, mental power, and time—time they could have spent with their loved ones. It made me feel lousy for not deciding to pay the same price. But this passage made it easier for me to appreciate my own decision.

&gt; Everything has its price. How much does lettuce cost? If you are unwilling to pay a dollar for lettuce, yet you envy the man who has a bagful of lettuce because he paid five dollars, you are a fool. Do not imagine he has ga</description>
    <pubDate>Sun, 09 Feb 2020 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category><category>book</category><category>philosophy</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/moneyland-book</guid>
    <title>Money Land - Why Thieves &amp; Crooks Now Rule the World &amp; How to Take it Back</title>
    <link>https://www.nico.fyi/blog/moneyland-book</link>
    <description>
![money-land-book-by-oliver-bullough](/static/images/articles/moneyland.jpg)

I just finished reading Money Land - Why Thieves &amp; Crooks Now Rule the World &amp; How to Take it Back book. In this book, Oliver Bullough set out this global virtual space where lawyers, business people, politicians, and ultra rich people exploit the world&#39;s law and financial systems for their own benefits.

&gt; Money flows across borders, but laws do not

I learned many things from this book: how the mega rich kept their wealth and got even richer through offshore and shell companies, loopholes in laws and regulations that lawyers and business people took advantage of to make their clients richer, how some countries are selling passports to the super rich, how the crooks used diplomatic immunity to secure their money, and many more.

&gt; If there is one thing we know about Money land, it is that it keeps mutating, it keeps expanding, and the wealthy keep finding new tunnels down into it.

It is infuriating to find</description>
    <pubDate>Mon, 03 Feb 2020 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category><category>book</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/2019-year-in-review</guid>
    <title>2019 Year In Review</title>
    <link>https://www.nico.fyi/blog/2019-year-in-review</link>
    <description>
At the end of 2018, I summarized the year as [the year of gaming and traveling for me](https://nico.fyi/articles/look-back-2018/). In 2019, I traveled as many times as in 2018, but I played more games! I didn&#39;t draw as many times as in 2018, but I read more books than before.

# Travel

![japan-indonesia-travel-2019](/static/images/articles/travels-2019.jpg)

This year I was still lucky enough to be able to travel to Japan few times to appreciate sakura on April, to enjoy the summer at the beach in Shirahama, and to celebrate end of the year in Kobe.

I also went to Indonesia few times to meet my mom, to catch up with my college friends, to restock my food supplies 🙈, and to start a new (second) job in a startup in Indonesia (remotely!).

![pokemon-go-fest-safari-zone-2019](/static/images/articles/pokemon-2019.jpg)

I participated in several Pokemon Go events too: Pokemon Go Fest in Dortmund, Pokemon Go Safari Zone in Singapore, and Pokemon Go Fest Yokohama. While I really enjoyed Si</description>
    <pubDate>Tue, 28 Jan 2020 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/learning-microcopy-from-apple-style-guide</guid>
    <title>Learning Microcopy from Apple Style Guide</title>
    <link>https://www.nico.fyi/blog/learning-microcopy-from-apple-style-guide</link>
    <description>
Few days ago, someone tweeted about this online document from Apple called [Apple Style Guide](https://help.apple.com/applestyleguide/). It&#39;s the first time I heard about it and I&#39;m pretty sure I&#39;m not the only one. After skimming the document, I found many interesting tidbits about [microcopy](https://uxplanet.org/microcopy-tiny-words-with-a-huge-ux-impact-90140acc6e42) and copywriting. So this weekend, I decided to read everything.

For native English speakers, the content of this Apple Style Guide might not be so interesting, but for those who use English as second language, this document could help you make your apps or products better, especially if your apps are using English.

If you don&#39;t have the time to read everything, I curated the document in this article. Most of the things mentioned in the document is to avoid confusion and ambiguity for the users of Apple apps. I picked some parts that I found interesting, or even new to me.

## activate, deactivate

Avoid; instead, us</description>
    <pubDate>Sun, 20 Oct 2019 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/dev-note-testing-react-components-async-code</guid>
    <title>[Dev Note] Testing React component which contains async code</title>
    <link>https://www.nico.fyi/blog/dev-note-testing-react-components-async-code</link>
    <description>
# Scenario

Say we need to make a React component that has following requirements:

- Fetch data from network when it&#39;s first mounted and show the fetched data if succeeds
- Show loading indicator while fetching
- Show error message if fetch fails
- Show a button when it&#39;s not fetching to refetch the data when user clicks on it.

Following those requirements, we make use of the [new and shiny React hooks](https://reactjs.org/docs/hooks-intro.html) and come up with the following component

```javascript
import React, { useState, useEffect } from &#39;react&#39;

const App = () =&gt; {
  const [isLoading, setIsLoading] = useState(false)
  const [lastChecked, setLastChecked] = useState(Date.now())
  const [data, setData] = useState(null)
  const [error, setError] = useState(null)

  useEffect(() =&gt; {
    setIsLoading(true)

    fetch(&#39;SOME URL&#39;)
      .then((response) =&gt; response.json())
      .then((result) =&gt; {
        setIsLoading(false)
        setData(result.results)
      })
      .catch((err</description>
    <pubDate>Sun, 06 Oct 2019 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>react</category><category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/wwdc19</guid>
    <title>WWDC19 -  Best WWDC yet (for me)</title>
    <link>https://www.nico.fyi/blog/wwdc19</link>
    <description>
![wwdc-19](/static/images/articles/wwdc19-1.jpg)

This year I’m one of the lucky developers who got the chance to attend Apple WWDC. This was my second WWDC and the first one which I attended with the lottery ticket. When I went for the first time in 2013, it was still the race of refreshing the web page.

This year WWDC was a blast and I’m writing this to tell you my experiences and why I hope I will win the lottery again next year.

# WWDC19 Swag

![wwdc-19-free-swag](/static/images/articles/wwdc19-8.jpg)

This year&#39;s free jacket was pretty cool. It&#39;s reversible and it has emojis on one side. We also got some pins on registration. Everyone got different pins.

![wwdc-19-store](/static/images/articles/wwdc19-9.jpg)

But the t-shirts in the WWDC store were more awesome. The exploding memojis art on the t-shirts were fabulous. There were a long line on the first day of the opening of the store since everyone didn&#39;t want to miss getting the size of their choices. On the last day, there </description>
    <pubDate>Sun, 09 Jun 2019 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/april-may-2019-brain-feeding</guid>
    <title>Brain Feeding/Poisoning - 2019年4月 5月</title>
    <link>https://www.nico.fyi/blog/april-may-2019-brain-feeding</link>
    <description>
April was a traveling month. We went to Japan, Indonesia, and Singapore. We arrived in Japan at the perfect time when the sakura was blooming. Pro tip: If you go to Tokyo to enjoy sakura, Nakameguro river is the place you need to go. The sakura succession in Nakameguro is jaw dropping.

![nakameguro-sakura](/static/images/articles/nakameguro.jpg)

In Singapore we went to Sentosa island for the first Pokemon Go Safari Zone in South East Asia! It was extremely hot 🥵. But it was fun to play Pokemon Go with my Indonesian friends in person.

![pokemon-go-safari-zone-singapore](/static/images/articles/pokemon-go-safari-zone-singapore.jpg)

Before going back to Switzerland, we explored the new mall in Changi airport, Jewel. It was pretty awesome. There was the first Pokemon center store outside Japan. And also there was an unbelievably amazing indoor water fall in it 🤯 .

![rain-vortex-jewel-singapore](/static/images/articles/rain-vortex-jewel-singapore.jpg)

# Play

Did you know Japan has</description>
    <pubDate>Tue, 07 May 2019 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/configuring-new-mac</guid>
    <title>Configuring New Mac</title>
    <link>https://www.nico.fyi/blog/configuring-new-mac</link>
    <description>
![new-mac-mini](/static/images/articles/new-mac-mini.jpg)

I&#39;ve been thinking of buying a Mac mini to replace my Macbook Pro as my main computer at home. But it turned out I didn&#39;t need to buy since my company loaned me a Mac mini to use at home! So it&#39;s time to set up this thing. Setting up a new computer is both fun and annoying. There are some features that I wish were a default.

# Desktop

1. Enable dock magnification. (System Preferences ➡️ Dock)
2. Show the day of the week and the date in menu bar. (System Preferences ➡️ Date and Time ➡️ Clock)

# Keyboard

1. Add custom keyboard shortcut for switching to the other windows of the same app. I always use Option+Tab for this. (System Preferences ➡️ Keyboard ➡️ Shortcuts ➡️ Keyboard ➡️ Move Focus to The Next Window)
2. Change Full Keyboard Access to All Controls. (System Preferences ➡️ Keyboard ➡️ Shortcuts ➡️ All Controls)
3. Disable automatic spell correction. I usually type in three languages: English, Indonesian, and Japanese. </description>
    <pubDate>Tue, 07 May 2019 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/march-2019-brain-feeding</guid>
    <title>Brain Feeding/Poisoning - 2019年3月</title>
    <link>https://www.nico.fyi/blog/march-2019-brain-feeding</link>
    <description>
Late post of the brain feeding series for March 2019 since I&#39;ve been busy to finish some work before April&#39;s trip to Japan, Indonesia, and Singapore! But better late than never, right?

# Play

![jump-force](/static/images/articles/jump-force.jpg)

I&#39;ve been playing [Jump Force](https://g.co/kgs/wSR6NR) a little bit on PS4. It&#39;s pretty interesting to play as my favourite manga characters, like Luffy of One Piece and Goku of Dragon Ball. The battle game play is pretty fun too. It&#39;s also a relief that I can play it offline and that it has a story line. I&#39;m not a fan of online multiplayer game. My only gripe is the clunky movement of the character when not in a battle.

![Xenoblade Chronicles 2: Torna The Golden Country](/static/images/articles/torna.jpg)

I continued playing [Torna The Golden Country of Xenoblade Chronicles 2](https://www.nintendo.com/games/detail/xenoblade-chronicles-2-torna-the-golden-country-switch) on Nintendo Switch. It&#39;s been a few months since I finished Xenoblad</description>
    <pubDate>Sun, 24 Mar 2019 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/dev-note-publish-private-npm-package-to-private-repo</guid>
    <title>[Dev Note] Publish private NPM package to private git repo</title>
    <link>https://www.nico.fyi/blog/dev-note-publish-private-npm-package-to-private-repo</link>
    <description>
Have you heard that [github is giving unlimited private repositories to all github users](https://github.blog/2019-01-07-new-year-new-github/)? Thanks, [Microsoft](https://github.blog/2018-10-26-github-and-microsoft/)! Unlimited private repositories is the only reason I put all my hobby projects in Gitlab. But now I can probably put some projects in Github as well.

Speaking of private thing, have you ever wanted to publish a private npm package for internal use? You can use [npm private registry for $7/month](https://www.npmjs.com/pricing). Or you can also use private git repository (in Github, Gitlab, own server, etc) for **free**.

The (probably) not-so-secret ingredient is [publish-to-git](https://github.com/Rush/publish-to-git).

# Installation

Install publish-to-git locally as dev dependency

```
npm i --save-dev publish-to-git
```

or globally

```
npm i -g publish-to-git
```

# Usage

Edit your package.json file in your project

```
&quot;scripts&quot;: {
// ... other scripts
  &quot;publis</description>
    <pubDate>Tue, 12 Feb 2019 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/february-2019-brain-feeding</guid>
    <title>Brain Feeding/Poisoning - 2019年2月</title>
    <link>https://www.nico.fyi/blog/february-2019-brain-feeding</link>
    <description>
Second month of the year! Time flies. Since the [first post for 2019 brain feeding series](https://nico.fyi/articles/january-2019-brain-feeding/), I have

✅ finished [Valkyrie Chronicles 4](https://g.co/kgs/kopFf6) on Nintendo Switch! I love the gameplay, the artwork, and the story of the game. Well done.

✅ reached the final part of [Horizon Zero Dawn](https://g.co/kgs/LjVptk) on PS4.

✅ finished listening to [The Order Of Time](https://g.co/kgs/5TiSAh) audio book.

✅ caught a perfect [Palkia in Pokemon Go](https://pokemongolive.com/en/post/legendary-palkia/)!

❌ not finished [Skin in the Game](https://g.co/kgs/o6NL11) book.

# Read

![the-order-of-time](/static/images/articles/the-order-of-time.jpg)

I got so hooked by [The Order Of Time](https://g.co/kgs/5TiSAh) audio book that I ended up buying the real book. I wanted to catch the bits I missed from listening the audio book. I like not only the content of the book, but also the size and the cover. It&#39;s small and light, and the cov</description>
    <pubDate>Sun, 10 Feb 2019 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/dev-note-git-edit-commit-drone-cypress</guid>
    <title>[Dev Note] Edit Git Commit Message and Cypress test crashes with Droneio</title>
    <link>https://www.nico.fyi/blog/dev-note-git-edit-commit-drone-cypress</link>
    <description>
# Edit Git Commit Message

Few days ago I needed to edit a commit message. If the commit had been the last commit, I could&#39;ve run

```
git commit --amend
```

But it was not. Found out I could use git rebase instead:

```
git rebase -i &lt;hash&gt;^
```

`-i` means interactive. Don&#39;t forget to add the carret (^) after the commit hash. It will then open the text editor where it shows a list of commits and the actions for each of them. The default is `pick` action. You just need to change the action of the commit whose message you want to edit with `reword`. Save the file then it will open another text editor where you can actually edit the commit message.

The commit I made had been pushed to github. So I needed to forcefully push the message by `git push origin --force`

# Cypress Crash with DroneIO

In the company I set up a continuous integration server for our projects using [DroneIO](https://drone.io/). And for the landing page of [itheorie.ch](https://itheorie.ch) website, I am using [</description>
    <pubDate>Sat, 09 Feb 2019 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>cypress</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/january-2019-brain-feeding</guid>
    <title>Brain Feeding/Poisoning - 2019年1月</title>
    <link>https://www.nico.fyi/blog/january-2019-brain-feeding</link>
    <description>
The first out of (hopefully) 12 blog post series about what I read, listen to, watch, and play every month.

# Play

![valkyrie-horizon-zero-dawn-pokemon](/static/images/articles/january-2019-play.JPG)

I&#39;m into three games at this moment. On Nintendo Switch, I&#39;m enjoying Valkyrie 4 Chronicles, an anime-style turn-based strategy game. What&#39;s not to like 😉 I have never played any Valkyrie games before but thankfully I don&#39;t need to to play this game.

On PS4, I&#39;m playing Horizon Zero Dawn. The graphics of this game is amazing. The story is intriguing so far. The battle is a bit difficult for me but I&#39;m getting used to it.

On mobile, I&#39;m continuing to play Pokemon Go. The new player-vs-player battle is pretty fun especially since I&#39;m in a weekly tournament against my college friends. I won the first week tournament but probably won&#39;t win the second week since seems like my friends learned some new tricks. 🤔

# Read

![skin-in-the-game](/static/images/articles/skin-in-the-game.JPG)

I</description>
    <pubDate>Tue, 08 Jan 2019 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/look-back-2018</guid>
    <title>2018, Year of gaming and Japan</title>
    <link>https://www.nico.fyi/blog/look-back-2018</link>
    <description>
2018 has been an interesting year which can be summarized in three words: travels, plays, and drawing. Looking back, it was a productive (and successful) year in a different kind of way I used to define productivity or success. I used to think that success means releasing an app or project, or making a certain amount of money. But sometime in 2018, I decided (quietly, and probably unconsciously too) to try something different. That something turned out to be gaming. You see, I used to feel guilty when I play games. It felt like I was just wasting my time. But I had been feeling like that most of my life and I realized it didn&#39;t actually change anything significant in my life. So I thought what if I just play games, and just enjoy it. And I just did.

# 1 月 - Let the game begin

![new-year-in-basel](/static/images/articles/01-firework.jpeg)

We started the year by viewing the fireworks in Rhine River in Basel. It was slightly raining but there were many people watching and partying.

!</description>
    <pubDate>Mon, 31 Dec 2018 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>personal</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/typesafety-localization-for-react-app</guid>
    <title>Type-Safe Localization for React App Using Flow</title>
    <link>https://www.nico.fyi/blog/typesafety-localization-for-react-app</link>
    <description>
## The Challenge

Switzerland has four national languages: German, French, Italian, and Romansh. The first three languages are the languages which are used by the majority of the people. For that reason most web apps I made here need to support at least those three and English in some.

When developing our first web app, I explored the popular React library for internationalization, [React Intl](https://github.com/yahoo/react-intl). But I decided not to use it since it&#39;s overkill for our simple requirement: we just need to display different strings based on the selected language.

## Keep it simple!

Our solution was just to use a simple Javascript object which contains the strings for each of the languages.

```javascript
// @flow
// locales.js file
// you can separate the translations object into different files if you want

export const de = {
  title: &#39;iTheorie Online-Lernen&#39;,
  welcomeTo: &#39;Willkommen bei $title&#39;,
  // and so on ...
}
export type StringsType = typeof de

export co</description>
    <pubDate>Sun, 02 Dec 2018 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>dev</category><category>react</category>
  </item>

  <item>
    <guid>https://www.nico.fyi/blog/shortcuts-save-instagram-photos-videos</guid>
    <title>iOS Shortcuts to download public Instagram photos and videos</title>
    <link>https://www.nico.fyi/blog/shortcuts-save-instagram-photos-videos</link>
    <description>
I made this iOS Shortcuts to download public photos and videos from Instagram. Unfortunately, Instagram for iOS does not use iOS Sharing sheet so this Shortcut cannot be accessed quickly. But fortunately we can put Shortcut in Today&#39;s widget of iOS. Once it&#39;s shown in the widget, we just need to copy the instagram link, swipe down from the top left corner of the iPhone or iPad, then swipe right to reveal the widget, and tap the Shortcut.

Get the Shortcut [here](https://www.icloud.com/shortcuts/6d09d61fd09942bea4400a66497caa8d). Check out the demo below (&lt;a href=&quot;./instagram-shortcuts-demo.mp4&quot;&gt;link to the video&lt;/a&gt;).

&lt;video controls muted&gt;
  &lt;source src=&quot;./instagram-shortcuts-demo.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;p&gt;
    Your browser doesn&#39;t support HTML5 video. Here is a{&#39; &#39;}
    &lt;a href=&quot;./instagram-shortcuts-demo.mp4&quot;&gt;link to the video&lt;/a&gt; instead.
  &lt;/p&gt;
&lt;/video&gt;
</description>
    <pubDate>Mon, 12 Nov 2018 00:00:00 GMT</pubDate>
    <author>hi@nico.fyi (Nico Prananta)</author>
    <category>ios</category>
  </item>

    </channel>
  </rss>
