{"id":10323,"date":"2026-09-16T07:58:31","date_gmt":"2026-09-15T22:58:31","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=10323"},"modified":"2026-09-17T14:55:38","modified_gmt":"2026-09-17T05:55:38","slug":"nextjs-sns-server31","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/16\/nextjs-sns-server31\/","title":{"rendered":"[nextjs]SNS Server-31(myapp41)\u00a0"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb myapp41\uc5d0\uc11c\ub294 SNS\uba54\uc778\ud398\uc774\uc9c0\uc5d0 \ud5e4\ub354\ubd80\ubd84\uacfc \ud314\ub85c\uc6b0\ubc84\ud2bc\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4.<br>In myapp41, a header section and a &#8220;Follow&#8221; button are added to the social media main page.<\/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\"><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 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\n\u2502   \u251c\u2500\u2500 api\/timeline\/route.ts  -&gt; Timeline 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 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/timelines\/home\/route.ts<\/span> -&gt; timeline\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;\">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\/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\/auth\/signup\/route.ts        -&gt; Signup\n\u2502   \u251c\u2500\u2500 api\/auth\/login\/route.ts         -&gt; Login\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/logout\/route.ts        -&gt; Logout\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/refresh\/route.ts       -&gt; Refresh Token\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\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 pinafore\/PinaforeTheme.tsx -&gt; Theme 1\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   \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\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=\"WJlNPrnWHb\"><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=48iGJ2Omto#?secret=WJlNPrnWHb\" data-secret=\"WJlNPrnWHb\" 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 \ucf54\ub4dc \uc218\uc815 \/ Code Modification<\/p>\n\n\n\n<p>\u2714\ufe0f \ud504\ub85c\ud544 \ud5e4\ub354 \ucd94\uac00,\uc2e4\uc2dc\uac04 \uce74\uc6b4\ud2b8 \ubc18\uc601<br>Added profile header; implemented real-time count updates.<\/p>\n\n\n\n<p>&#8212; app\/usersui\/[username]\/_components\/themes\/pinafore\/PinaforeTheme.tsx<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>... ...\nfunction FollowButton({ username, initialFollowing, currentUser, onToggle }: {\n... ...\n  return (\n    &lt;button\n      onClick={toggle}\n      disabled={loading}\n      className={following? 'profile-edit-btn' : 'profile-follow-btn'}\n    &gt;\n      {following? 'Following' : 'Follow'}\n    &lt;\/button&gt;\n  );\n }\n\nexport function PinaforeTheme({ timeline, username, onBoost, onLike }: any) {\n\n  return (\n    &lt;&gt;\n     &lt;style&gt;{``}&lt;\/style&gt;\n    &lt;\/&gt;\n... ...\n         {\/* \u2705 myapp41 - Timeline Header *\/}\n         &lt;div className=\"profile-header\"&gt; ... &lt;\/div&gt;\n... ...\n                &lt;div&gt;\n                  {\/* - \ub85c\uadf8\uc778\ud558\uba74 \ud504\ub85c\ud544 \ud3b8\uc9d1 \ub85c\uadf8\uc778 \ud6c4 \ud314\ub85c\uc6b0\ubc84\ud2bc \/ Log in to edit profile; follow button appears after logging in. *\/ }\n                  {isMe? (\n                    &lt;&gt;\n                    {\/* \ud314\ub85c\uc6b0,\uc5b8\ud314\ub85c\uc6b0 \ubc84\ud2bc \/ follow, unfollow button *\/ }\n                    {\/* &lt;FollowButton username={username} initialFollowing={profile?.following || false} currentUser={currentUser} \/&gt; *\/}\n                    &lt;button className=\"profile-edit-btn\"&gt;\ud504\ub85c\ud544 \ud3b8\uc9d1\/Edit Profile&lt;\/button&gt;\n                    &lt;\/&gt;\n                  ) : ( \n                      &lt;FollowButton username={username} \n                        initialFollowing={profile?.following || false} \n                        currentUser={currentUser} \n                        onToggle={handleFollowToggle} \n                      \/&gt;)}\n                &lt;\/div&gt;\n )\n}\n<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f user1\uc73c\ub85c \uc544\uc774\ub514 \uace0\uc815 \uc218\uc815, \ud314\ub85c\uc6b0 \ud655\uc778 json\ud544\ub4dc \ucd94\uac00<br>Fixed the user ID to &#8216;user1&#8217; and added a JSON field to check follow status.<\/p>\n\n\n\n<p>&#8212;  app\/api\/v1\/accounts\/[id]\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>... ...\nexport async function GET(req: Request, { params }: { params: Promise&lt;{ id: string }&gt; }) {    \n\n    \/\/ parameter User - \u2705 myapp41\n    <strong>const me = getUsernameFromRequest(req);<\/strong>\n    \/\/ const tempUserid = req.headers.get('x-user') || 'user1';  \/\/ \u2705 \ub85c\uadf8\uc778 \uc774\uc804 \uc784\uc2dc \uc544\uc774\ud53c \ubcc0\uc218 \/ Temporary ID variable before login\n\n\n\/\/ \ud314\ub85c\uc6b0 \uc5ec\ubd80 \/ Follow status\n    let isFollowing = false;\n    if (me &amp;&amp; me!== targetUsername) {\n      const f = db.prepare(`SELECT 1 FROM following WHERE username =? AND actor LIKE ?`).get(me, `%${targetUsername}%`) as any;\n      console.log(`\u2705 accounts-username:${me},actor:${targetUsername}`);\n      if (f) isFollowing = true;\n    }\n    console.log(`me:${me},targetUsername:${targetUsername}`);\n    return NextResponse.json({\n      id: rawId,\n      username: targetUsername,\n      acct: `${targetUsername}@${DOMAIN}`,\n      display_name: userRow?.display_name || targetUsername,\n      avatar: `https:\/\/${DOMAIN}\/icon.png`,\n      avatar_static: `https:\/\/${DOMAIN}\/icon.png`,\n      header: `https:\/\/picsum.photos\/seed\/${DOMAIN}_${targetUsername}\/1200\/400`,\n      header_static: `https:\/\/picsum.photos\/seed\/${DOMAIN}_${targetUsername}\/1200\/400`,\n      followers_count: cntFollowers?.cnt || 0,\n      following_count: cntFollowing?.cnt || 0,\n      statuses_count: cntPosts?.cnt || 0,\n      note: userRow?.summary? `&lt;p&gt;${userRow.summary}&lt;\/p&gt;` : `&lt;p&gt;${targetUsername}&lt;\/p&gt;`,\n      url: `https:\/\/${DOMAIN}\/users\/${targetUsername}`,\n      emojis: &#91;], fields: &#91;], bot: false,\n      created_at: userRow?.created_at || new Date().toISOString(),\n     <strong> following: isFollowing, \/\/ \u2705 FollowButton\uc6a9!<\/strong>\n      is_me: me === targetUsername\n    }, { headers: { 'Access-Control-Allow-Origin': '*' } });\n\n  } catch (e) {\n    console.error(e);\n    return NextResponse.json({ error: 'failed' }, { status: 500, headers: { 'Access-Control-Allow-Origin': '*' , 'Cache-Control': 'no-store, no-cache, must-revalidate',} });\n  }\n... ...\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \ucfe0\ud0a4 \ub514\ucf54\ub529 \uc2e4\ud328 \uc218\uc815 \/ Fixed cookie decoding failure.<\/p>\n\n\n\n<p>&#8212; lib\/auth.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>\/\/ \u2705 myapp39 + myapp40 + myapp41 - username return from request\nexport function getUsernameFromRequest(req: Request | NextRequest): string | null { ... \n    \/\/ token \ucfe0\ud0a4\/Cookie\n    const tokenCookie = cookies&#91;'token'];\n    if (tokenCookie) {\n      const decoded = verifyToken(tokenCookie);\n      if (decoded?.username) {\n        console.log(`\u2705 me from token (verify): ${decoded.username}`);\n        return decoded.username;\n      }\n      const payload = <strong>decodeJwtPayloadNoVerify(tokenCookie);<\/strong>\n      if (payload?.username) {\n        console.log(`\u2705 me from token (no-verify): ${payload.username}`);\n        return payload.username;\n      }\n    }\n...\n}\n\n\/\/ \u2705 myapp41 - verify \uc5c6\uc774 payload\ub9cc \uc77d\ub294 \ud568\uc218\n\/\/ A function that reads only the payload without verification.\nfunction decodeJwtPayloadNoVerify(token: string): any {\n  try {\n    const parts = token.split('.');\n    if (parts.length!== 3) return null;\n    let payload = parts&#91;1];\n    payload = payload.replace(\/-\/g, '+').replace(\/_\/g, '\/');\n    while (payload.length % 4) payload += '=';\n    const json = Buffer.from(payload, 'base64').toString('utf-8');\n    return JSON.parse(json);\n  } catch {\n    return null;\n  }\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \ud314\ub85c\uc6b0\ub098 \uc5b8\ud314\ub85c\uc6b0\uc2dc \ud55c\ucabd \ud14c\uc774\ube14\ub9cc \uc815\ubcf4 \ucd94\uac00 \uc0ad\uc81c\ub418\ub294\uac83 \uc218\uc815<br>Fixed the issue where information was added or deleted in only one table when following or unfollowing.<\/p>\n\n\n\n<p>&#8212; \/api\/follow\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>...\n\/\/ \ud314\ub85c\uc6b0 API \/ follow API\nexport async function POST(req: Request) {\n    \/\/ ===== 1. \ub85c\uceec \ud314\ub85c\uc6b0 \/ Local Follow =====\n    if (localTarget) {\n<strong>      \/\/ following: \ub0b4\uac00 \ud314\ub85c\uc6b0\ud558\ub294 \ubaa9\ub85d\n      \/\/ Following: The list of people I follow<\/strong>\n      try {\n        db.prepare(`INSERT OR IGNORE INTO following (id, actor, inbox, username) VALUES (?,?,?,?)`)\n         .run(id, targetActor, `${targetActor}\/inbox`, username);\n      } catch {\n        \/\/ \uad6c \uc2a4\ud0a4\ub9c8 \ud638\ud658 (inbox \uceec\ub7fc \uc5c6\ub294 \ubc84\uc804)\n        \/\/ Legacy schema compatibility (version without the 'inbox' column)\n        db.prepare(`INSERT OR IGNORE INTO following (id, actor, username) VALUES (?,?,?)`)\n         .run(id, targetActor, username);\n      }\n\n<strong>      \/\/ followers: \uc0c1\ub300\ubc29\uc758 \ud314\ub85c\uc6cc \ubaa9\ub85d\n      \/\/ followers: The other party's list of followers<\/strong>\n      try {\n        db.prepare(`INSERT OR IGNORE INTO followers (id, actor, inbox, username) VALUES (?,?,?,?)`)\n         .run(`${localTarget}-${username}-${Date.now()}`, myActor, `${myActor}\/inbox`, localTarget);\n      } catch {\n        db.prepare(`INSERT OR IGNORE INTO followers (id, actor, username) VALUES (?,?,?)`)\n         .run(`${localTarget}-${username}-${Date.now()}`, myActor, localTarget);\n      }\n    }\n    \/\/ ===== 2. \ub9ac\ubaa8\ud2b8 \ud314\ub85c\uc6b0 \/ Remote Follow =====\n    const actorRes = await signedFetch(target, username);\n}\n\n\/\/ \uc5b8\ud314\ub85c\uc6b0 \/ Unfollow\nexport async function DELETE(req: Request) {\n... ...\n    if (localTarget) {\n      <strong>\/\/ \ub85c\uceec\uc740 DB\uc5d0\uc11c\ub9cc \uc0ad\uc81c \/ Delete from the database only (local).<\/strong>\n      db.prepare(`DELETE FROM following WHERE username=? AND (actor LIKE? OR actor=?)`).run(username, `%\/users\/${localTarget}%`, localTarget);\n      db.prepare(`DELETE FROM followers WHERE username=? AND (actor LIKE? OR actor=?)`).run(localTarget, `%\/users\/${username}%`, username);\n      console.log(`\ud83d\uddd1 \ub85c\uceec \uc5b8\ud314\ub85c\uc6b0: ${username} -X-&gt; ${localTarget}`);\n      return Response.json({ ok: true, local: true });\n    }\n... ...\n   <strong> \/\/ \ub9ac\ubaa8\ud2b8 \uc5b8\ud314\ub85c\uc6b0 \/ remote unfollow<\/strong>\n... ...\n    const { sendUndoFollow } = await import('@\/lib\/ap');\n    const result = await sendUndoFollow(target, username);\n\n    db.prepare('DELETE FROM following WHERE username=? AND (actor=? OR actor LIKE?)').run(username, target, `%${target}%`);\n\n}<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc2a4\ud2b8 \/ Test<\/p>\n\n\n\n<p>\u2714\ufe0f user1\ub85c\uadf8\uc778\ud558\uace0 user2 \ud314\ub85c\uc6b0<br>Log in as user1 and follow user2.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"554\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user2-following-jpg.jpg\" alt=\"\" class=\"wp-image-10355\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user2-following-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user2-following-jpg-300x208.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user2-following-jpg-768x532.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user2-following-jpg-400x277.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">\/@user2<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"490\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user1-following-jpg.jpg\" alt=\"\" class=\"wp-image-10357\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user1-following-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user1-following-jpg-300x184.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user1-following-jpg-768x470.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user1-following-jpg-400x245.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">\/@user1<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f user1\ub85c\uadf8\uc778\ud558\uace0 user2 \uc5b8\ud314\ub85c\uc6b0<br>Log in as user1 and unfollow user2.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"551\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user1-unfollow-jpg.jpg\" alt=\"\" class=\"wp-image-10361\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user1-unfollow-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user1-unfollow-jpg-300x207.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user1-unfollow-jpg-768x529.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user1-unfollow-jpg-400x276.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">\/@user1<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"609\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user2-follow.jpg\" alt=\"\" class=\"wp-image-10365\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user2-follow.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user2-follow-300x228.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user2-follow-768x585.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-user2-follow-400x305.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">@user2<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f \uc5b8\ud314\ub85c\uc6b0 \uc11c\ubc84\ub85c\uadf8 \/ Unfollow server log<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"168\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-serverlog-unfollow-jpg.jpg\" alt=\"\" class=\"wp-image-10371\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-serverlog-unfollow-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-serverlog-unfollow-jpg-300x63.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-serverlog-unfollow-jpg-768x161.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/09\/myapp41-serverlog-unfollow-jpg-400x84.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Server Log<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f Database<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>sqlite&gt; SELECT username, actor FROM following;\n<strong>user1|https:\/\/aloy-horizon.duckdns.org\/users\/user2<\/strong>\nuser1|https:\/\/freelifemakers.com\/users\/user1\nuser1|https:\/\/mastodon.social\/users\/freelifemakers\nsqlite&gt; \nsqlite&gt; \nsqlite&gt; SELECT username, actor FROM followers;\n<strong>user2|https:\/\/aloy-horizon.duckdns.org\/users\/user1<\/strong>\nuser1|https:\/\/freelifemakers.com\/users\/user1\nuser1|https:\/\/mastodon.social\/ap\/users\/117193449750714993\nsqlite&gt; <\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\uc694\ud55c\ubcf5\uc74c 8\uc7a5 32\uc808 \/ John 8:32<\/p>\n\n\n\n<p>&#8220;\uadf8\ub9ac\uace0 \ub108\ud76c\ub294 \uc9c4\ub9ac\ub97c \uc54c\uac8c \ub420 \uac83\uc774\uba70, \uc9c4\ub9ac\uac00 \ub108\ud76c\ub97c \uc790\uc720\ub86d\uac8c \ud560 \uac83\uc774\ub2e4.&#8221;<\/p>\n\n\n\n<p>&#8220;Then you will know the truth ,and the truth will set you free&#8221;<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb myapp41\uc5d0\uc11c\ub294 SNS\uba54\uc778\ud398\uc774\uc9c0\uc5d0 \ud5e4\ub354\ubd80\ubd84\uacfc \ud314\ub85c\uc6b0\ubc84\ud2bc\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4.In myapp41, a header section and a &#8220;Follow&#8221; button are added to the social media main page. \ud83d\udc49\ud83c\udffb \uc804\uccb4 \ucf54\ub4dc\ub294 \uae43\ud5c8\ube0c\uc5d0\uc11c \ud655\uc778 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.You can find the full code on GitHub. https:\/\/github.com\/gideonslife01\/flm-nextjs \ud83d\udcc1 \uc804\uccb4 \ud504\ub85c\uc81d\ud2b8 \uad6c\uc870 \/ Overall Project Structure \ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc2dc\uc791 \/ Project Start \ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":10357,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,27,1,7],"tags":[],"class_list":["post-10323","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-news","category-nextjs","category-uncategorized","category-website","has-thumbnail"],"_links":{"self":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/10323","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=10323"}],"version-history":[{"count":38,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/10323\/revisions"}],"predecessor-version":[{"id":10372,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/10323\/revisions\/10372"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media\/10357"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=10323"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=10323"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=10323"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}