{"id":8066,"date":"2026-08-16T15:05:15","date_gmt":"2026-08-16T06:05:15","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=8066"},"modified":"2026-08-17T16:04:36","modified_gmt":"2026-08-17T07:04:36","slug":"nextjs-sns-server-4-2","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/08\/16\/nextjs-sns-server-4-2\/","title":{"rendered":"[nextjs]SNS Server-5(myapp15)"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb \uc774\ubc88\uc5d0\ub294 @user1@yourhost.domain.org\uc758 \ud314\ub85c\uc6cc \ubaa9\ub85d\uc744 \ub9cc\ub4ed\ub2c8\ub2e4.<br>Next, create a list of followers for @user1@yourhost.domain.org.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb pinafore.social\ud504\ub85c\ud544\uc758 FOLLOWERS 1\uc744 \ud074\ub9ad\ud560\uacbd\uc6b0 \ubaa9\ub85d\uc5d0 \ubcf4\uc5ec\uc8fc\ub294 \uae30\ub2a5\uc785\ub2c8\ub2e4.<br>This feature displays a list when you click &#8220;FOLLOWERS 1&#8221; on the pinafore.social profile.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb gotosocial\uc740 \uc815\ucc45\uc0c1 remote \uacc4\uc815(\ub2e4\ub978 \uc11c\ubc84 \uacc4\uc815\uc758 \ud314\ub85c\uc6cc) \uc815\ubcf4\ub294 \ubcf4\uc5ec\uc8fc\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.<br>Due to its policy, GoToSocial does not display information about remote accounts (followers from other servers).<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uadf8\ub798\uc11c \ub77c\uc6b0\ud130\ub97c \ub9cc\ub4e4\uace0 API \ud655\uc778\ub9cc \ud560 \uc608\uc815\uc785\ub2c8\ub2e4.<br>So, I plan to create the router and just verify the API.<\/p>\n\n\n\n<p>\ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc124\uce58,\ub77c\uc774\ube0c\ub7ec\ub9ac \uc124\uce58,HTTPS\uc124\uc815<br>Project setup, library installation, HTTPS configuration<\/p>\n\n\n\n<p>\u2714\ufe0f \uc544\ub798\uc758 \uc774\uc804 \ud3ec\uc2a4\ud2b8\ub97c \ucc38\uc870\ud558\uc138\uc694<br>Please refer to the previous post below.<\/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=\"auwn6xHsZK\"><a href=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/08\/10\/nextjs-dns-caddy\/\">[nextjs]nextjs+domain(ddns)+https(caddy)<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;[nextjs]nextjs+domain(ddns)+https(caddy)&#8221; &#8212; freelifemakers.org\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/08\/10\/nextjs-dns-caddy\/embed\/#?secret=uiippusnYR#?secret=auwn6xHsZK\" data-secret=\"auwn6xHsZK\" width=\"560\" height=\"315\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\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>myapp15\/  \n\u251c\u2500\u2500 app\/  (Next.js App Router)\n\u2502   \u251c\u2500\u2500 api\/posts\/route.ts     -&gt; \uae00 \uc4f0\uae30 API \/ Writing API\n\u2502   \u251c\u2500\u2500 api\/follow\/route.ts    -&gt; \ud314\ub85c\uc6b0 API(\uc784\uc2dc) \/ Follow API(temporary)\n\u2502   \u251c\u2500\u2500 users\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 route.ts           -&gt; Actor\uc815\ubcf4 \/ Acotr Information\n\u2502   \u2502   \u251c\u2500\u2500 followers\/route.ts -&gt; Followers 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 layout.tsx, page.tsx, globals.css\n\u2502   \u2514\u2500\u2500 favicon.ico\n\u251c\u2500\u2500 lib\/\n\u2502   \u251c\u2500\u2500 ap.ts                  -&gt; Follow Accept\n\u2502   \u2514\u2500\u2500 db.ts                  -&gt; DB connection\n\u251c\u2500\u2500 data\/\n\u2502   \u2514\u2500\u2500 keys\/                  -&gt; private.pem, public.pem\n\u251c\u2500\u2500 data.sqlite                -&gt; Database\n\u251c\u2500\u2500 Caddyfile                  -&gt; https \n\u2514\u2500\u2500 package.json<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 followers \ub77c\uc6b0\ud130 \ub9cc\ub4e4\uae30<br>Creating a followers router<\/p>\n\n\n\n<p>\u2714\ufe0f followers\ub97c \ubcf4\uc5ec\uc8fc\uae30\ud558\uae30 \uc704\ud55c \ubaa9\ub85d\uc744 \ub9cc\ub4ed\ub2c8\ub2e4.<br>Create a list to display followers.<\/p>\n\n\n\n<p>\u2714\ufe0f myapp15\/app\/users\/[username]\/followers\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ myapp14\/app\/api\/follow\/route.ts\nimport { sendFollow, signedFetch } from '@\/lib\/ap';\n\n\/\/const DOMAIN = process.env.DOMAIN || 'aloy-horizon.duckdns.org';\n\n\/\/ myapp14  \u2705 - Follow \ubcf4\ub0b4\uae30 API \/ Send Follow API\n\/\/ POST \/api\/follow\nexport async function POST(req: Request) {\n  try {\n    const { username = 'user1', target } = await req.json();\n    if (!target) return Response.json({ error: 'target \ud544\uc694' }, { status: 400 });\n\n    console.log(`\u27a1\ufe0f \ud314\ub85c\uc6b0 \uc2dc\ub3c4 \/ Follow attempt: ${username} -&gt; ${target}`);\n\n    \/\/ 1. \uc0c1\ub300\ubc29 inbox \ucc3e\uae30 - \uc11c\uba85\ub41c GET\uc73c\ub85c! \/ Find the inbox with a signed GET request\n    const actorRes = await signedFetch(target, username);\n    \n    if (!actorRes.ok) {\n      const t = await actorRes.text();\n      return Response.json({ error: `\uc0c1\ub300\ubc29 \uc870\ud68c \uc2e4\ud328 \/ Failed to look up the other party. ${actorRes.status}`, body: t }, { status: 400 });\n    }\n\n    const actor = await actorRes.json();\n    const inbox = actor.inbox;\n    console.log(`\ud83d\udcec inbox \ucc3e\uc74c \/ Found inbox: ${inbox}`);\n\n    \/\/ 2. Follow \uc804\uc1a1 \/ Send Follow\n    const result = await sendFollow(inbox, target, username);\n\n    return Response.json({ \n      ok: result.ok, \n      inbox, \n      target,\n      result: result.text,\n      follow: result.followDoc\n    });\n\n  } catch (e: any) {\n    console.error('follow \uc5d0\ub7ec \/ Follow error:', e);\n    return Response.json({ error: e.message }, { status: 500 });\n  }\n}<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 \/ Code modification<\/p>\n\n\n\n<p>\u2714\ufe0f pinafore\uc5d0\uc11c \ud314\ub85c\uc6b0 \uc2e0\uccad\uc744 \ud558\uba74 aloy-horizon.duckdns.org\ub85c \ud314\ub85c\uc6b0 \uc694\uccad\uc774 \uc635\ub2c8\ub2e4.<br>When you send a follow request from Pinafore, the request is sent to aloy-horizon.duckdns.org.<\/p>\n\n\n\n<p>\u2714\ufe0f \uc774\ub584 \ud314\ub85c\uc6b0 \uc694\uccad\ud55c \uc0ac\uc6a9\uc790\uc758 \uc774\ub984\uc744 aloy-horizon.duckdns.org\uc5d0 \uc800\uc7a5\ud574\uc57c\ud569\ub2c8\ub2e4.<br>At this point, you need to save the name of the user who sent the follow request to aloy-horizon.duckdns.org.<\/p>\n\n\n\n<p>\u2714\ufe0f \uc774\uc804\uc5d0 \ub370\uc774\ud130\ubca0\uc774\uc2a4\uc5d0 username\uceec\ub7fc\uc774 \uc5c6\ub2e4\uba74 \ucd94\uac00 \ud569\ub2c8\ub2e4.<br>If the <code>username<\/code> column does not already exist in the database, add it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ALTER TABLE followers ADD COLUMN username TEXT;<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f username\uc5d0 \uc0ac\uc6a9\uc790 \uc544\uc774\ub514\ub97c \uc785\ub825\ud558\ub294 \ubd80\ubd84\uc758 \ucf54\ub4dc\ub97c \uc218\uc815\ud569\ub2c8\ub2e4.<br>Modify the code for the section where the user ID is entered into &#8216;username&#8217;.<\/p>\n\n\n\n<p>&#8212; myapp15\/app\/users\/[username]\/followers\/route.ts<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (body.type === 'Undo') {\n  db.prepare('DELETE FROM followers WHERE actor = ? AND username = ?').run(unfollowActorId, username);\n}\n\nif (body.type === 'Follow') {\n  db.prepare('INSERT OR IGNORE INTO followers (id, actor, inbox, username) VALUES (?,?,?,?)').run(body.id, actorId, inboxUrl, username);\n}\n\n<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud314\ub85c\uc6cc \ub370\uc774\ud130 \ud655\uc778\ud558\uae30<br>Check follower data<\/p>\n\n\n\n<p>\u2714\ufe0f pinafore.social\uc5d0\uc11c \uc0c8\ub85c \ud314\ub85c\uc6b0\ub97c \ud574\ubd05\ub2c8\ub2e4. \uadf8\ub798\uc11c \ub370\uc774\ud130\ubca0\uc774\uc2a4\uc640 API\uc5d0 \uc815\uc0c1\uc801\uc73c\ub85c \uc720\uc800\uac00 \ud655\uc778 \uac00\ub2a5\ud55c\uc9c0 \uccb4\ud06c\ud574\ubd05\ub2c8\ub2e4.<br>I\u2019m trying out a new follow on pinafore.social, so I\u2019m checking to see if the user is correctly recognized by the database and API.<\/p>\n\n\n\n<p>\u2714\ufe0f \ub370\uc774\ud130 \ubca0\uc774\uc2a4 \uc800\uc7a5\ud655\uc778\ud558\uae30<br>Verify database storage<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sqlite&gt; select * from followers;\nhttps:&#47;&#47;freelifemakers.com\/users\/user1\/follow\/01S310S4A222J6FN8QDQXJR2221|https:\/\/freelifemakers.com\/users\/user1|https:\/\/freelifemakers.com\/users\/user1\/inbox|user1\nsqlite&gt; <\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f API \ucd9c\ub825\ud655\uc778\ud558\uae30<br>Check API Output<\/p>\n\n\n\n<p>&#8212; <a href=\"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers\">https:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers<\/a><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"@context\": \"https:\/\/www.w3.org\/ns\/activitystreams\",\n  \"id\": \"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers\",\n  \"type\": \"OrderedCollection\",\n  \"totalItems\": 1,\n  \"orderedItems\": &#91;\n    \"https:\/\/freelifemakers.com\/users\/user1\"\n  ],\n  \"first\": {\n    \"id\": \"https:\/\/aloy-horizon.duckdns.org\/users\/user1\/followers?page=1\",\n    \"type\": \"OrderedCollectionPage\",\n    \"totalItems\": 1,\n    \"orderedItems\": &#91;\n      \"https:\/\/freelifemakers.com\/users\/user1\"\n    ]\n  }\n}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"513\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp15-1-jpg.jpg\" alt=\"\" class=\"wp-image-8105\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp15-1-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp15-1-jpg-300x192.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp15-1-jpg-768x492.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/myapp15-1-jpg-400x257.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">API<\/figcaption><\/figure>\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\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb \uc774\ubc88\uc5d0\ub294 @user1@yourhost.domain.org\uc758 \ud314\ub85c\uc6cc \ubaa9\ub85d\uc744 \ub9cc\ub4ed\ub2c8\ub2e4.Next, create a list of followers for @user1@yourhost.domain.org. \ud83d\udc49\ud83c\udffb pinafore.social\ud504\ub85c\ud544\uc758 FOLLOWERS 1\uc744 \ud074\ub9ad\ud560\uacbd\uc6b0 \ubaa9\ub85d\uc5d0 \ubcf4\uc5ec\uc8fc\ub294 \uae30\ub2a5\uc785\ub2c8\ub2e4.This feature displays a list when you click &#8220;FOLLOWERS 1&#8221; on the pinafore.social profile. \ud83d\udc49\ud83c\udffb gotosocial\uc740 \uc815\ucc45\uc0c1 remote \uacc4\uc815(\ub2e4\ub978 \uc11c\ubc84 \uacc4\uc815\uc758 \ud314\ub85c\uc6cc) \uc815\ubcf4\ub294 \ubcf4\uc5ec\uc8fc\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.Due to its policy, GoToSocial does not display information about remote [&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-8066","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\/8066","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=8066"}],"version-history":[{"count":38,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/8066\/revisions"}],"predecessor-version":[{"id":8108,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/8066\/revisions\/8108"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=8066"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=8066"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=8066"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}