Creating Pagination in Nuxt 3

Nuxt

Language :

Hi, I’m Lovefield.

This article is about pagination. Pagination is an essential feature for any page that displays posts or structured information. Because of its importance, it is considered a fundamental functionality.

First, create a Pagination.vue file inside the components folder.

Pagination.vue

HTML, XML

<template>
    <div class=”pagination”></div>
</template>

<script setup lang=”ts”>
</script>

To calculate pagination details, we need the currently active page and the total number of pages. We use defineProps to receive this information. Additionally, pagination should only be displayed if the total number of pages is greater than 1.

We also need to receive the URL information required for navigation. The URL is provided in the format "/board/$", where $ will be replaced with the page number.

HTML, XML

<template>
    <div v-if="props.totalPage > 1" class=”pagination”></div>
</template>

<script setup lang=”ts”>
interface PagenationData {
   url: string;
   active: number;
   totalPage: number;
}

const props = defineProps<PagenationData>();
</script>

Define variables to store the computed pagination data and ensure that calculations are performed whenever totalPage changes or when the component is loaded. This can be achieved using Vue's computed properties or watch to reactively update the pagination details.

TypeScript

const step = ref<number>(0);
const totalStep = ref<number>(0);
const startNumber = ref<number>(1);
const numberList = ref<number[]>([]);
const itemCount: number = 5;

function init(): void {
}

init();

watch(
    () => props.totalPage,
    () => {
        init();
    }
);

Assuming that the pagination displays numbers from 1 to 5, this corresponds to the value of itemCount. In this case, the range from 1 to 5 is considered a single step. Based on the active page, the appropriate step is determined, and the necessary values are computed to render the pagination correctly on the screen.

TypeScript

function init(): void {
   const stepData: number = Math.ceil(props.active / itemCount);
   const totalStepData: number = Math.ceil(props.totalPage / itemCount);
   const startNumberData: number = stepData * itemCount - (itemCount - 1);
   const numberListData: number[] = [];

   for (let i: number = startNumberData; i < startNumberData + itemCount; i += 1) {
       if (i > props.totalPage) {
           break;
       }

       numberListData.push(i);
   }

   step.value = stepData;
   totalStep.value = totalStepData;
   startNumber.value = startNumberData;
   numberList.value = numberListData;
}

Since all the necessary values for rendering have been calculated, it's time to structure the rendering. The pagination will include buttons to navigate to the previous and next steps, as well as buttons for pages 1 through 5. Each button will be assigned the movePageEvent function to ensure that clicking it redirects to the corresponding page.

HTML, XML

<template>
   <div v-if="props.totalPage > 1" class="pagination">
       <button class="link" :disabled="step === 1" @click="movePageEvent(startNumber - 1)">&lt;</button>
       <button
           v-for="(item, count) in numberList"
           class="link"
           :class="{ '--active': item === props.active }"
           :key="`btn-page${count}`"
           @click="movePageEvent(item)"
       >
           {{ item }}
       </button>
       <button class="link" :disabled="step === totalStep" @click="movePageEvent(startNumber + itemCount)">
           &gt;
       </button>
   </div>
</template>

Now, let's write the function to handle page navigation. This function will replace the $ in the provided URL with the selected page number and redirect the user accordingly.

TypeScript

const route = useRoute();

async function movePageEvent(idx: number): Promise<void> {
   await navigateTo({
       path: props.url.replace("$", String(idx)),
       query: route.query,
       replace: true,
   });
}

Once the component is complete, you can now use it as follows:

HTML, XML

<Pagination url="/board/free/$`" :active="1" :total="50" />

Here’s the final version of the Pagination component code.

Pagination.vue

HTML, XML

<template>
   <div v-if="props.totalPage > 1" class="pagination">
       <button class="link" :disabled="step === 1" @click="movePageEvent(startNumber - 1)">&lt;</button>
       <button
           v-for="(item, count) in numberList"
           class="link"
           :class="{ '--active': item === props.active }"
           :key="`btn-page${count}`"
           @click="movePageEvent(item)"
       >
           {{ item }}
       </button>
       <button class="link" :disabled="step === totalStep" @click="movePageEvent(startNumber + itemCount)">
           &gt;
       </button>
   </div>
</template>

<script setup lang="ts">
interface PagenationData {
   url: string;
   active: number;
   totalPage: number;
}

const route = useRoute();
const props = defineProps<PagenationData>();
const step = ref<number>(0);
const totalStep = ref<number>(0);
const startNumber = ref<number>(1);
const numberList = ref<number[]>([]);
const itemCount: number = 5;

function init(): void {
   const stepData: number = Math.ceil(props.active / itemCount);
   const totalStepData: number = Math.ceil(props.totalPage / itemCount);
   const startNumberData: number = stepData * itemCount - (itemCount - 1);
   const numberListData: number[] = [];

   for (let i: number = startNumberData; i < startNumberData + itemCount; i += 1) {
       if (i > props.totalPage) {
           break;
       }

       numberListData.push(i);
   }

   step.value = stepData;
   totalStep.value = totalStepData;
   startNumber.value = startNumberData;
   numberList.value = numberListData;
}

init();

async function movePageEvent(idx: number): Promise<void> {
   await navigateTo({
       path: props.url.replace("$", String(idx)),
       query: route.query,
       replace: true,
   });
}

watch(
   () => props.totalPage,
   () => {
       init();
   }
);
</script>

Lovefield

Web Front-End developer

하고싶은게 많고, 나만의 서비스를 만들고 싶은 변태스러운 개발자입니다.