{"id":11818,"date":"2026-10-08T08:32:51","date_gmt":"2026-10-07T23:32:51","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=11818"},"modified":"2026-10-08T15:14:21","modified_gmt":"2026-10-08T06:14:21","slug":"nextjs-sns-server-49","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/10\/08\/nextjs-sns-server-49\/","title":{"rendered":"[nextjs]SNS Server-49(myapp59)\u00a0"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb myapp59\ub294 \ucee4\uc11c\ud398\uc774\uc9c0 \ub124\uc774\uc158\uc744 \uad6c\ud604\ud569\ub2c8\ub2e4.(\ubb34\ud55c\uc2a4\ud06c\ub864 + \ud398\uc774\uc9c0\ub124\uc774\uc158)<br>myapp59 implements cursor-based pagination (combining infinite scroll and pagination).<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb themeex \ud14c\ub9c8 CSS\ub97c Tailwindcss\ub85c \ubcc0\ud658\ud588\uc2b5\ub2c8\ub2e4.<br>I converted the themeex theme CSS to Tailwind CSS.<br>(FollowersList.tsx FollowingList.tsx NotificationsList.tsx Themetheme.tsx TimelineList.tsx)<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \ud398\uc774\uc9c0\ub124\uc774\uc158\uc740 themeex\ud14c\ub9c8 \ub0b4\uc758 FollowersList.tsx ,FollowingList.tsx ,NotificationsList.tsx ,TimelineList.tsx \ud30c\uc77c\uc5d0 \uc801\uc6a9\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.<br>Pagination has been implemented in the FollowersList.tsx, FollowingList.tsx, NotificationsList.tsx, and TimelineList.tsx files within the themeex theme.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uc804\uccb4 \ucf54\ub4dc\ub294 \uae43\ud5c8\ube0c\uc5d0\uc11c \ud655\uc778 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<br>You can find the full code on GitHub.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/gideonslife01\/flm-nextjs\">https:\/\/github.com\/gideonslife01\/flm-nextjs<\/a><\/p>\n\n\n\n<p>\ud83d\udcc1 \uc804\uccb4 \ud504\ub85c\uc81d\ud2b8 \uad6c\uc870 \/ Overall Project Structure<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>myapp project\/  \n\u251c\u2500\u2500 app\/  (Next.js App Router)\n\u2502   \u251c\u2500\u2500 .well-known\/webfinger\/route.ts  -&gt; webfinger\n\u2502   \u251c\u2500\u2500 <strong>hooks\/useCursorPagination.ts    -&gt; Pagination Hook<\/strong>\n\u2502   \u251c\u2500\u2500 api\/follow\/route.ts    -&gt; Follow API(temporary)\n\u2502   \u251c\u2500\u2500 api\/announce\/route.ts  -&gt; Boost(Announcement)\n\u2502   \u251c\u2500\u2500 api\/like\/route.ts.     -&gt; Like API\n\u2502   \u251c\u2500\u2500 api\/posts\/route.ts     -&gt; Writing API(deprecated)\n\u2502   \u251c\u2500\u2500 api\/timeline\/route.ts  -&gt; Timeline API\n\u2502   \u251c\u2500\u2500 api\/followinglist\/route.ts  -&gt; followinglist API\n\u2502   \u251c\u2500\u2500 api\/followerslist\/route.ts  -&gt; followerslist API\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/instance<\/span>\/route.ts -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/apps\/route.ts.    -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/verify_credentials\/route.ts -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/statuses\/route.ts -&gt; Write Post\n\u2502   \u251c\u2500\u2500 <strong><span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/timelines\/_home_back\/route.ts<\/span> -&gt; timeline(Depreacted)<\/strong>\n\u2502   \u251c\u2500\u2500 api\/v1\/media\/route.ts -&gt; Image upload\n\u2502   \u251c\u2500\u2500 api\/v1\/media\/&#91;id]\/route.ts  -&gt; Image Delete \n\u2502   \u251c\u2500\u2500 api\/v1\/instance\/rules\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/instance\/domain_blocks\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/filters\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/lists\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/announcements\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 <strong>api\/v1\/timelines\/_public_back\/route.ts -&gt; Feature not implemented.(Deprecated)<\/strong>\n\u2502   \u251c\u2500\u2500 <strong>api\/v1\/timelines\/&#91;type]\/route.ts -&gt; Timeline API<\/strong>\n\u2502   \u251c\u2500\u2500 api\/v1\/statuses\/&#91;id]\/context\/route.ts -&gt; Post Check,Delete\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/authorize\/route.ts<\/span> -&gt; auth\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/token\/route.ts<\/span> -&gt; auth\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/login-and-authorize\/route.ts<\/span> -&gt; Tursky,Pinafore login processing\n\u2502   \u251c\u2500\u2500 oauth\/revoke\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/search\/home\/route.ts<\/span> -&gt; search\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v2\/search\/home\/route.ts<\/span> -&gt; search\n\u2502   \u251c\u2500\u2500 api\/v2\/notifications\/policy\/route.ts-&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/&#91;id]\/followers\/route.ts -&gt; followers\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/&#91;id]\/following\/route.ts -&gt; following\n\u2502   \u251c\u2500\u2500 api\/v1\/statuses\/&#91;id]\/route.ts -&gt; -&gt; Post delete(pinafore)\n\u2502   \u251c\u2500\u2500 api\/auth\/signup\/route.ts        -&gt; Website Signup\n\u2502   \u251c\u2500\u2500 api\/auth\/login\/route.ts         -&gt; Website Login\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/logout\/route.ts        -&gt; Website Logout\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/refresh\/route.ts       -&gt; Refresh Token\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/cleanup-orphan\/route.ts     -&gt; post,outbox clean up\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/me\/route.tsx           -&gt; Login Check<strong>\n<\/strong>\u2502   \u251c\u2500\u2500 auth\/signup\/page.tsx            -&gt; Signup UI\n\u2502   \u251c\u2500\u2500 auth\/signup\/page.tsx            -&gt; Login UI        \n\u2502   \u251c\u2500\u2500 users\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 statuses\/&#91;id]\/route.ts -&gt; Indivisual Post\n\u2502   \u2502   \u251c\u2500\u2500 route.ts               -&gt; Acotr Information\n\u2502   \u2502   \u251c\u2500\u2500 followers\/route.ts     -&gt; Followers List\n\u2502   \u2502   \u251c\u2500\u2500 following\/route.ts     -&gt; Following List\n\u2502   \u2502   \u251c\u2500\u2500 inbox\/route.ts         -&gt; Inbox\n\u2502   \u2502   \u2514\u2500\u2500 outbox\/route.ts        -&gt; outbox\n\u2502   \u251c\u2500\u2500 usersui\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 page.tsx               -&gt; Timeline UI(Server, 404)\n\u2502   \u2502   \u251c\u2500\u2500 ClientPage.tsx.        -&gt; Timeline UI(Client,Call Active Themes)\n\u2502   \u2502   \u2514\u2500\u2500 _components\/themes\/\n\u2502   \u2502       \u251c\u2500\u2500 themeex\/ThemeexTheme.tsx      -&gt; Example Theme\n\u2502   \u2502       \u251c\u2500\u2500 themeex\/FollowersList.tsx     -&gt; Theme Example, FollowersList\n\u2502   \u2502       \u251c\u2500\u2500 themeex\/FollowingList.tsx     -&gt; Theme Example, FollowingList\n\u2502   \u2502       \u251c\u2500\u2500 themeex\/NotificationsList.tsx -&gt; Theme Example, NotificationsList\n\u2502   \u2502       \u251c\u2500\u2500 themeex\/TimelineList.tsx      -&gt; Theme Example, TimelineList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/PinaforeTheme.tsx -&gt; Theme 1\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/FollowersList.tsx -&gt; Theme 1, FollowersList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/FollowingList.tsx -&gt; Theme 1, FollowingList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/NotificationsList.tsx -&gt; Theme 1, NotificationsList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/TimelineList.tsx  -&gt; Theme 1, TimelineList\n\u2502   \u2502       \u251c\u2500\u2500 mastodon\/MastodonTheme.tsx -&gt; Theme 2\n\u2502   \u2502       \u2514\u2500\u2500 minimal\/MinimaltTheme.tsx  -&gt; Theme 3\n\u2502   \u251c\u2500\u2500 layout.tsx, page.tsx, globals.css\n\u2502   \u2514\u2500\u2500 favicon.ico\n\u251c\u2500\u2500 lib\/\n\u2502   \u251c\u2500\u2500 theme.tsx              -&gt; Theme Provider\n\u2502   \u251c\u2500\u2500 watchThemes.ts         -&gt; Check real-time theme changes\n\u2502   \u251c\u2500\u2500 auth.ts                -&gt; Authentication, User Management\n\u2502   \u251c\u2500\u2500 ap.ts                  -&gt; Follow,Undo,Create,Likes,Announce\n\u2502   \u251c\u2500\u2500 visibility.ts          -&gt; Visibility(Public,Unlisted,Private,Direct)\n\u2502   \u2514\u2500\u2500 db.ts                  -&gt; DB connection\n\u251c\u2500\u2500 data\/\n\u2502   \u251c\u2500\u2500 keys\/userIDs\/          -&gt; private.pem, public.pem(New)\n\u2502   \u2514\u2500\u2500 keys\/                  -&gt; private.pem, public.pem(legacy)\n\u251c\u2500\u2500 data.sqlite                -&gt; Database(1\/3)\n\u251c\u2500\u2500 data.sqlite-wal            -&gt; Database(2\/3)\n\u251c\u2500\u2500 data.sqlite-shm            -&gt; Database(3\/3)\n\u251c\u2500\u2500 Caddyfile                  -&gt; https \n\u251c\u2500\u2500 instrumentation.ts         -&gt; Background Server\n\u251c\u2500\u2500 proxy.ts                   -&gt; middleware.ts - &gt;proxy.ts(for Nextjs16)\n\u251c\u2500\u2500 postcss.config.mjs         -&gt; for Nextjs16\n\u2514\u2500\u2500 package.json<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc2dc\uc791 \/ Project Start<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-freelifemakers-org wp-block-embed-freelifemakers-org\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"8LaMeso0p7\"><a href=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/03\/nextjs-project-summary\/\">[nextjs]\ud504\ub85c\uc81d\ud2b8\uc694\uc57d\/project summary(myapp30+)<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;[nextjs]\ud504\ub85c\uc81d\ud2b8\uc694\uc57d\/project summary(myapp30+)&#8221; &#8212; freelifemakers.org\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/03\/nextjs-project-summary\/embed\/#?secret=Ih9BV3ie9u#?secret=8LaMeso0p7\" data-secret=\"8LaMeso0p7\" width=\"560\" height=\"315\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\ud83d\udcc1 \ub77c\uc6b0\ud2b8 \ucd94\uac00 \ubc0f \ucf54\ub4dc \uc218\uc815\/ Add route and code modification<\/p>\n\n\n\n<p>\u2714\ufe0f app\/hooks\/useCursorPagination.ts &#8211; \ub77c\uc6b0\ud2b8 \ucd94\uac00 \/ Add route<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>\/\/ app\/hooks\/useCursorPagination.ts\n\/\/ \u2705 myapp59 \n\/\/ - \ubaa8\ub4e0 \ub9ac\uc2a4\ud2b8\uc5d0\uc11c \uc7ac\uc0ac\uc6a9 (react-query \uc0ac\uc6a9\ud558\uc9c0 \uc54a\uc74c) \/ Reusable across all lists (without using React Query)\n\n'use client';\nimport { useState, useCallback, useEffect } from 'react';\n\ntype CursorResponse&lt;T&gt; = {\n  data: T&#91;];\n  nextCursor: string | null;\n  hasMore: boolean;\n};\n\ntype UseCursorOptions&lt;T&gt; = {\n  key: (string | number | undefined)&#91;];\n  fetcher: (cursor?: string) =&gt; Promise&lt;CursorResponse&lt;T&gt;&gt;;\n  initialData?: T&#91;];\n};\n\nexport function useCursorPagination&lt;T&gt;({ key, fetcher, initialData = &#91;] }: UseCursorOptions&lt;T&gt;) {\n  const &#91;pages, setPages] = useState&lt;T&#91;]&#91;]&gt;(initialData.length? &#91;initialData] : &#91;]);\n  const &#91;nextCursor, setNextCursor] = useState&lt;string | null&gt;(null);\n  const &#91;hasNextPage, setHasNextPage] = useState(true);\n  const &#91;isFetchingNextPage, setIsFetchingNextPage] = useState(false);\n  const &#91;isLoading, setIsLoading] = useState(pages.length === 0);\n\n  \/\/ key\uac00 \ubc14\ub00c\uba74 \ub9ac\uc14b \/ Reset if the key changes.\n  \/\/ key = home,federated,local\n  useEffect(() =&gt; {\n    setPages(initialData.length? &#91;initialData] : &#91;]);\n    setNextCursor(null);\n    setHasNextPage(true);\n    setIsLoading(initialData.length === 0);\n  }, &#91;JSON.stringify(key)]);\n\n  const fetchNextPage = useCallback(async () =&gt; {\n    if (isFetchingNextPage ||!hasNextPage) return;\n    setIsFetchingNextPage(true);\n    try {\n      const res = await fetcher(nextCursor || undefined);\n      setPages(prev =&gt; &#91;...prev, res.data]);\n      setNextCursor(res.nextCursor);\n      setHasNextPage(res.hasMore);\n    } catch (e) {\n      console.error(e);\n    } finally {\n      setIsFetchingNextPage(false);\n      setIsLoading(false);\n    }\n  }, &#91;fetcher, nextCursor, hasNextPage, isFetchingNextPage]);\n\n  \/\/ \uccab \ub85c\ub4dc \/ first load\n  useEffect(() =&gt; {\n    if (pages.length === 0) {\n      fetchNextPage();\n    }\n  }, &#91;]);\n\n  const flatData = pages.flat();\n\n  return {\n    flatData,\n    pages: pages.map(data =&gt; ({ data })),\n    fetchNextPage,\n    hasNextPage,\n    isFetchingNextPage,\n    isLoading,\n  };\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f app\/api\/v1\/timelines\/[type]\/route.ts &#8211; \uc678\ubd80\uc571\uc6a9 \ud0c0\uc784\ub77c\uc778 \/ Timeline for external apps<\/p>\n\n\n\n<p>&#8212; \uc774\uc804 \ud504\ub85c\uc81d\ud2b8\uae4c\uc9c0 timelines\/public timelines\/home\uc73c\ub85c \uc0ac\uc6a9\ud588\uc73c\ub098 [type]\uc5d0 \ub514\ub809\ud1a0\ub9ac \uc774\ub984\uc744 \ub9c8\uc6b4\ud2b8 \ud558\ub3c4\ub85d \ubcc0\uacbd<br>Previously, the path <code>timelines\/public timelines\/home<\/code> was used, but this has been changed to mount the directory name to <code>[type]<\/code>.<\/p>\n\n\n\n<p>&#8212; \uc804\uccb4 \ucf54\ub4dc\ub294 \uae43\ud5c8\ube0c  \ucc38\uc870<br>Refer to GitHub for the full code.<\/p>\n\n\n\n<p>&#8212; <code>created_at<\/code> \uac12\uc774 \uc5b4\ub5a4 \ud615\ud0dc\ub85c \uc624\ub4e0 \uc22b\uc790\ub85c \ub41c \ud0c0\uc784\uc2a4\ud0ec\ud504(ms)\ub85c \uc14b\ud305\ud558\ub294 \ud568\uc218<br>A function that sets the <code>created_at<\/code> value to a numeric timestamp (in milliseconds), regardless of the input format.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>function parseDate(v: any): number {\n  if (!v) return 0;\n  if (\/^\\d{13}$\/.test(String(v))) return parseInt(v);\n  const d = new Date(v).getTime();\n  return isNaN(d) ? 0 : d;\n}<\/code><\/pre>\n\n\n\n<p style=\"font-size:16px\">1)if (\/^\\d{13}$\/.test(String(v))) return parseInt(v); <\/p>\n\n\n\n<p style=\"font-size:16px\">A. 13\uc790\ub9ac \uc22b\uc790(\ud0c0\uc784\uc2a4\ud0ec\ud504)\ub85c \uc800\uc7a5\ub41c \uacbd\uc6b0 parseInt\ub85c \uc22b\uc790\ub85c \ubcc0\ud658\ud574\uc11c \uc0ac\uc6a9<br>If stored as a 13-digit number (timestamp), convert it to a number using parseInt and use it.<\/p>\n\n\n\n<p style=\"font-size:16px\">2) const d = new Date(v).getTime();<\/p>\n\n\n\n<p style=\"font-size:16px\">A. Date \uac1d\uccb4\ub97c \uc774\uac78 getTime()\uc73c\ub85c \ubc00\ub9ac\ucd08\ub85c \ubcc0\ud658<br>Convert this Date object into milliseconds using getTime().<\/p>\n\n\n\n<p>&#8212; <code>sort((a,b) =&gt; ...)<\/code>\ub294 \uc815\ub82c \uae30\ub2a5\uc73c\ub85c \ub450 \uac12\uc744 \ube44\uad50\ud574\uc11c \uc21c\uc11c\ub97c \uc815\ud568<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>filteredRows.sort((a,b) =&gt; parseDate(b.created_at) - parseDate(a.created_at))<\/code><\/pre>\n\n\n\n<p class=\"has-small-font-size\">1) \ub9ac\ud134\uac12\uc774 \uc74c\uc218\uba74 <code>a<\/code>\uac00 \uc55e\uc73c\ub85c,\uc591\uc218\uba74 b\uac00 \uc55e\uc73c\ub85c,0\uc774\uba74 \uc21c\uc11c \uc720\uc9c0<br>If the return value is negative, a comes first; if positive, b comes first; if zero, the order is maintained.<\/p>\n\n\n\n<p class=\"has-small-font-size\">2)<code>b - a<\/code> = \ub0b4\ub9bc\ucc28\uc21c \/ Descending order( DESC) = \ucd5c\uc2e0\uae00\uc774 \uc704\ub85c&nbsp;\/ Latest posts top<\/p>\n\n\n\n<p class=\"has-small-font-size\">3)<code>a - b<\/code> = \uc624\ub984\ucc28\uc21c \/ Ascending order (ASC)  = \uc624\ub798\ub41c \uae00\uc774 \uc704\ub85c \/ Old text on top<\/p>\n\n\n\n<p class=\"has-small-font-size\">4) \ubc30\uc5f4\uc5d0 \ub2f4\uae34 \ub370\uc774\ud130\uc758 \ube44\uad50\ubc29\uc2dd\uacfc \ube44\uad50\ud69f\uc218\ub294 sort\ud568\uc218 \uc54c\uace0\ub9ac\uc998\uc5d0 \ub530\ub77c \uc2e4\ud589\ud568.<br>The method and number of comparisons for data stored in an array are determined by the algorithm of the sort function.<\/p>\n\n\n\n<p>&#8212;  nextCursonr \ubcc0\uc218 \uc14b\ud305 \/ Setting the nextCursor variable<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>  const hasMore = filteredRows.length &gt; limit;\n  const sliced = filteredRows.slice(0, limit);\n  const nextCursor = hasMore ? sliced&#91;sliced.length - 1]?.created_at : null;<\/code><\/pre>\n\n\n\n<p class=\"has-small-font-size\">1)limit\uc124\uc815 \uac12\ubcf4\ub2e4 \ub370\uc774\ud130\uac2f\uc218\uac00 \ub354 \ub9ce\uc73c\uba74 (hasMore) nextCursor\ub294 sliced\ubc30\uc5f4\uc5d0 \ub2f4\uae34 &#8216;\ub9c8\uc9c0\ub9c9 \ubc30\uc5f4 -1&#8217; \ubc88\uc9f8 \ubc30\uc5f4\uc758 create_at\uc744 nextCursor\ub85c \uc800\uc7a5\ud568<br>If the number of data items exceeds the <code>limit<\/code> setting (<code>hasMore<\/code> is true), the <code>nextCursor<\/code> is set to the <code>created_at<\/code> value of the element at the second-to-last position of the sliced \u200b\u200barray.<\/p>\n\n\n\n<p class=\"has-small-font-size\">2)\ubc30\uc5f4\uc740 0\ubc88 \ubd80\ud130 \uc2dc\uc791\ud558\uae30 \ub54c\ubb38\uc5d0 limit\uac00 20\uc774\uba74 \ub370\uc774\ud130\ub294 21\uac1c\ub97c \ubd88\ub7ec\uc634<br>Since arrays are zero-indexed, a limit of 20 results in 21 items of data being retrieved.<\/p>\n\n\n\n<p>&#8212; hasMore,nextCursor\ub97c NextResponse.json\uc5d0 \ud3ec\ud568\uc2dc\ucf1c\uc11c UI\uc5d0\uc11c \ub370\uc774\ud130\ub97c \ud638\ucd9c \ud558\ub3c4\ub85d \uc2e4\ud589<br>Include <code>hasMore<\/code> and <code>nextCursor<\/code> in the <code>NextResponse.json<\/code> so that the UI can fetch the data.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>  const headers = new Headers();\n  headers.set('Access-Control-Allow-Origin', '*');\n  headers.set('Content-Type', 'application\/json');\n  if (nextCursor) {\n    headers.set('Link', `&lt;https:\/\/${DOMAIN}\/api\/v1\/timelines\/${type}?max_id=${nextCursor}&amp;limit=${limit}&gt;; rel=\"next\"`);\n    headers.set('X-Next-Cursor', String(nextCursor)); \n  }\n\n  \/\/ \uc678\ubd80 \uc571\uc740 \ubc30\uc5f4\ub9cc \uae30\ub300, \ub0b4\ubd80 themeex\ub294 \ucee4\uc11c \ud544\uc694\ud558\uba74 ?format=cursor \ucd94\uac00\n  \/\/ External apps expect only an array; the internal themeex requires `?format=cursor` if a cursor is needed.\n  if (searchParams.get('format') === 'cursor') {\n    return NextResponse.json({ data: statuses, nextCursor, hasMore }, { headers });\n  }<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f app\/usersui\/[username]\/_components\/themes\/themeex\/TimelineList.tsx &#8211; \ub85c\uceec \ud0c0\uc784\ub77c\uc778\/Local Timeline<\/p>\n\n\n\n<p>&#8212;  \ud0c0\uc784\ub77c\uc778 \ucd08\uae30 \ub370\uc774\ud130 \ubd88\ub7ec\uc624\uae30,userCursorPagination\ud568\uc218 \ubd88\ub7ec\uc624\uae30<br>Load initial timeline data, call the userCursorPagination function.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code> \nexport default function TimelineList({ \n  timeline: initialTimeline, currentUser, tab, onTabChange, onDelete, onBoost, onLike, onShare }: Props) {\n  const &#91;showDropdown, setShowDropdown] = useState(false); \/\/ \u2705 myapp56 - Hom,Local,Federated \ud0ed \ucd94\uac00 \/ Add Hom, Local, Federated Tabs\n\n\n  \/\/ \u2705 myapp59 - useCurserPagination\n  const {\n    flatData,\n    fetchNextPage,\n    hasNextPage,\n    isFetchingNextPage,\n  } = useCursorPagination({\n    key: &#91;'timeline', tab],\n    fetcher: async (cursor) =&gt; {\n      const params = new URLSearchParams();\n      params.set('limit', '20');\n      params.set('type', tab);\n      if (cursor) params.set('max_id', cursor);\n      const res = await fetch(`\/api\/v1\/timelines\/${tab}?${params.toString()}`, { credentials: 'include' });\n      if (!res.ok) throw new Error('fetch fail');\n      return res.json();\n    },\n  }); \n... ...\n \n}<\/code><\/pre>\n\n\n\n<p>&#8212; loaderRef\ub85c \ub370\uc774\ud130 \ub05d\uc774 \uac10\uc9c0\ub418\uba74 fetchNextPage()\ud568\uc218 \uc2e4\ud589<br>Execute the <code>fetchNextPage()<\/code> function when the end of the data is detected via <code>loaderRef<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code> const<strong> loaderRef<\/strong> = useRef&lt;HTMLDivElement&gt;(null);\n  useEffect(() =&gt; {\n    const el = loaderRef.current;\n    if (!el ||!hasNextPage) return;\n    const observer = new IntersectionObserver(\n      (&#91;entry]) =&gt; {\n        if (entry.isIntersecting &amp;&amp;!isFetchingNextPage) <strong>fetchNextPage();<\/strong>\n      },\n      { threshold: 0.3 }\n    );\n    observer.observe(el);\n    return () =&gt; observer.disconnect();\n  }, &#91;hasNextPage, isFetchingNextPage, fetchNextPage]);<\/code><\/pre>\n\n\n\n<p>1)new IntersectionObserver\ub85c \ud398\uc774\uc9c0 \uc2a4\ud06c\ub864 \ub0b4\ub824\uc11c \uc5b4\ub514\uae4c\uc9c0 \uc654\ub294\uc9c0 \uacc4\uc18d \uac10\uc2dc<br>Use <code>new IntersectionObserver<\/code> to continuously monitor how far down the page has been scrolled.<\/p>\n\n\n\n<p>2){ threshold: 0.3 } \ud398\uc774\uc9c0 \ubc14\ub2e5\uae4c\uc9c0 30% \ub0a8\uc544 \uc788\uc73c\uba74 fetchNextPage()\ud568\uc218 \uc2e4\ud589<br>{ threshold: 0.3 } Execute the fetchNextPage() function when 30% of the page remains to the bottom.<\/p>\n\n\n\n<p>&#8212; \ub370\uc774\ud130 \ub05d\uc73c\ub85c \ub370\uc774\ud130 \ubd88\ub7ec\uc624\uae30 \uac10\uc9c0\ud558\ub294 \ubd80\ubd84<br>The section that detects the end of the data to trigger data loading.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>... ...      \n{\/* \u2705 myapp59 - \ubb34\ud55c\uc2a4\ud06c\ub864 \ud2b8\ub9ac\uac70 \/ Infinite scroll trigger *\/}\n      &lt;div ref={<strong>loaderRef<\/strong>} className=\"h-10 flex items-center justify-center text-sm text-&#91;#999]\"&gt;\n        {isFetchingNextPage &amp;&amp; &lt;span&gt;\ubd88\ub7ec\uc624\ub294 \uc911...&lt;\/span&gt;}\n        {!hasNextPage &amp;&amp; data.length &gt; 0 &amp;&amp; &lt;span className=\"text-xs\"&gt;\ub05d \/ No more posts&lt;\/span&gt;}\n      &lt;\/div&gt;<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\u2714\ufe0f app\/api\/v1\/statuses\/[id]\/context\/route.ts  &#8211; \ubc84\uadf8\uc218\uc815 \/ Bug fix<\/p>\n\n\n\n<p>&#8212; nextjs15\ubc84\uc804 \uc774\uc0c1\uc740 params: Promise\uc0ac\uc6a9<br>For Next.js version 15 or later, use <code>params: Promise<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>export async function GET(\n  req: Request,\n  { params }: { params: Promise&lt;{ id: string }&gt; } \n) {\n  const { id } = await params; \/\/ \u2705 myapp58 - nextjs15+\n  if (!id) {\n    return NextResponse.json({ ancestors: &#91;], descendants: &#91;] }, {\n      headers: { 'Access-Control-Allow-Origin': '*' }\n    });\n  } \n\n... ... }<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\ud83d\udcc1 \uc88b\uc544\uc694,\ubd80\uc2a4\ud2b8 \uae30\ub2a5 \uc218\uc815 \uc608\uc815<br>Okay, the boost function is scheduled to be modified.<\/p>\n\n\n\n<p>\u2714\ufe0f \ud398\uc774\uc9c0 \ub124\uc774\uc158 \uae30\ub2a5 \ucd94\uac00\ub85c \uc778\ud574 \uc88b\uc544\uc694,\ubd80\uc2a4\ud2b8 \uae30\ub2a5 \ubcc0\uacbd \uc218\uc815 \uc608\uc815<br>The &#8220;Like&#8221; and &#8220;Boost&#8221; functions are scheduled to be modified due to the addition of pagination.<\/p>\n\n\n\n<p>\u2714\ufe0f \uc88b\uc544\uc694 \/ favorite(=like)<\/p>\n\n\n\n<p>&#8212; \/api\/v1\/statuses\/[id]\/favourite<\/p>\n\n\n\n<p>\u2714\ufe0f \ubd80\uc2a4\ud2b8 \/ boost(=announce)<\/p>\n\n\n\n<p>&#8212; \/api\/v1\/statuses\/[id]\/reblog&nbsp;<\/p>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc2a4\ud2b8 \/ Test<\/p>\n\n\n\n<p>\u2714\ufe0f \uae30\uc874\uacfc \ub3d9\uc77c\ud558\uace0 \uc678\uad00\uc0c1 \ubc14\ub010 \ubd80\ubd84\uc774 \uc5c6\uc5b4\uc11c \ud14c\uc2a4\ud2b8 \uc774\ubbf8\uc9c0\ub294 \uc0dd\ub7b5\ud569\ub2c8\ub2e4.<br>It remains identical to the previous version with no visible changes, so I am omitting the test images.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\ub9c8\ud0dc\ubcf5\uc74c 21\uc7a5 22\uc808 \/ Matthew 21: 22<\/p>\n\n\n\n<p>&#8220;\ub108\ud76c\uac00 \uae30\ub3c4\ud560 \ub54c\uc5d0 \ubb34\uc5c7\uc774\ub4e0\uc9c0 \ubbff\uace0 \uad6c\ud558\ub294 \uac83\uc740 \ub2e4 \ubc1b\uc73c\ub9ac\ub77c \ud558\uc2dc\ub2c8\ub77c&#8221;<\/p>\n\n\n\n<p>&#8220;And everything you ask in prayer, believing, you will receive.&#8221;<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb myapp59\ub294 \ucee4\uc11c\ud398\uc774\uc9c0 \ub124\uc774\uc158\uc744 \uad6c\ud604\ud569\ub2c8\ub2e4.(\ubb34\ud55c\uc2a4\ud06c\ub864 + \ud398\uc774\uc9c0\ub124\uc774\uc158)myapp59 implements cursor-based pagination (combining infinite scroll and pagination). \ud83d\udc49\ud83c\udffb themeex \ud14c\ub9c8 CSS\ub97c Tailwindcss\ub85c \ubcc0\ud658\ud588\uc2b5\ub2c8\ub2e4.I converted the themeex theme CSS to Tailwind CSS.(FollowersList.tsx FollowingList.tsx NotificationsList.tsx Themetheme.tsx TimelineList.tsx) \ud83d\udc49\ud83c\udffb \ud398\uc774\uc9c0\ub124\uc774\uc158\uc740 themeex\ud14c\ub9c8 \ub0b4\uc758 FollowersList.tsx ,FollowingList.tsx ,NotificationsList.tsx ,TimelineList.tsx \ud30c\uc77c\uc5d0 \uc801\uc6a9\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.Pagination has been implemented in the FollowersList.tsx, FollowingList.tsx, NotificationsList.tsx, and TimelineList.tsx files [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,27,1,7],"tags":[],"class_list":["post-11818","post","type-post","status-publish","format-standard","hentry","category-news","category-nextjs","category-uncategorized","category-website","missing-thumbnail"],"_links":{"self":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/11818","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/comments?post=11818"}],"version-history":[{"count":65,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/11818\/revisions"}],"predecessor-version":[{"id":11886,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/11818\/revisions\/11886"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=11818"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=11818"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=11818"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}