{"id":7266,"date":"2026-08-04T12:07:31","date_gmt":"2026-08-04T03:07:31","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=7266"},"modified":"2026-08-04T14:25:17","modified_gmt":"2026-08-04T05:25:17","slug":"nextjs-guestbook-markdown","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/08\/04\/nextjs-guestbook-markdown\/","title":{"rendered":"[nextjs]\ubc29\uba85\ub85d +  markdown\ud30c\uc77c \/ Guestbook + Markdown file"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb \uc774\uc804 \ud3ec\uc2a4\ud2b8\uc758  \uae30\uc874 \ubc29\uba85\ub85d\uc5d0\uc11c \ub370\uc774\ud130\ub97c md(markdown)\ud30c\uc77c\uc5d0 \uc800\uc7a5\ud569\ub2c8\ub2e4.<br>Data from the existing guestbook in the previous post is saved to an MD (Markdown) file.<\/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=\"MKvCyQlxnR\"><a href=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/07\/31\/nextjs-approute-pageroute\/\">[nextjs]\uc571\ub77c\uc6b0\ud2b8,\ud398\uc774\uc9c0\ub77c\uc6b0\ud2b8\/App Route,Page Route<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;[nextjs]\uc571\ub77c\uc6b0\ud2b8,\ud398\uc774\uc9c0\ub77c\uc6b0\ud2b8\/App Route,Page Route&#8221; &#8212; freelifemakers.org\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/07\/31\/nextjs-approute-pageroute\/embed\/#?secret=o3WVCpc8Bk#?secret=MKvCyQlxnR\" data-secret=\"MKvCyQlxnR\" width=\"560\" height=\"315\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uc5ec\uae30\uc11c\ub294 \uc571 \ub77c\uc6b0\ud2b8 \ubc29\uc2dd\uc744 \uc0ac\uc6a9\ud569\ub2c8\ub2e4.<br>Here, the App Router approach is used.<\/p>\n\n\n\n<p>\ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc124\uce58 \/ Project Installation<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npx create-next-app@latest<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 \/ Code modification<\/p>\n\n\n\n<p>\u2714\ufe0f route.js<\/p>\n\n\n\n<p>&#8212; GET\ud568\uc218 \/ GET function<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export async function GET() {\n  const postsDirectory = path.join(process.cwd(), 'app', 'posts');\n\n  if (!fs.existsSync(postsDirectory)) {\n    return NextResponse.json(&#91;]);\n  }\n\n  try {\n    const fileNames = fs.readdirSync(postsDirectory);\n    const mdFiles = fileNames.filter(fileName => fileName.endsWith('.md'));\n\n    \/\/ \uac01 \ud30c\uc77c\uc758 \uc0c1\uc138 \uc815\ubcf4(\uc218\uc815 \uc2dc\uac04 \ub4f1)\ub97c \ud3ec\ud568\ud55c \uac1d\uccb4 \ubc30\uc5f4 \ub9cc\ub4e4\uae30\n    \/\/ Create an array of objects containing detailed information (modification time, etc.) for each file\n    \n    const allPostsData = mdFiles.map((fileName) => {\n      const slug = fileName.replace(\/\\.md$\/, '');\n      const fullPath = path.join(postsDirectory, fileName);\n      \n      \/\/ \ud30c\uc77c\uc758 \uc0c1\ud0dc \uc815\ubcf4(\uc0dd\uc131\/\uc218\uc815 \uc2dc\uac04 \ub4f1)\ub97c \uac00\uc838\uc635\ub2c8\ub2e4.\n      \/\/ Retrieves file status information (creation\/modification time, etc.).\n      const stat = fs.statSync(fullPath);\n      const fileContents = fs.readFileSync(fullPath, 'utf8');\n\n      return {\n        slug,\n        content: fileContents,\n\n        \/\/ \uc815\ub82c \uae30\uc900\uc73c\ub85c \uc0bc\uae30 \uc704\ud574 \ud30c\uc77c \uc218\uc815 \uc2dc\uac04\uc744 \uc22b\uc790\ub85c \ubcc0\ud658\ud558\uc5ec \ubcf4\uad00\ud569\ub2c8\ub2e4.\n        \/\/ Convert the file modification time to a number and store it to use as a sorting criterion.\n        dateValue: stat.mtime.getTime(), \n      };\n    });\n\n    \/\/ \ucd5c\uc2e0 \uc218\uc815\/\uc0dd\uc131 \uc2dc\uac04 \uae30\uc900\uc73c\ub85c \ub0b4\ub9bc\ucc28\uc21c(\ucd5c\uc2e0\uc21c) \uc815\ub82c\ud558\uae30\n    \/\/ Sort in descending order (latest first) based on the latest modification\/creation time\n    allPostsData.sort((a, b) => b.dateValue - a.dateValue);\n\n    \/\/ \ud504\ub860\ud2b8\uc5d4\ub4dc\ub85c \ub370\uc774\ud130\ub97c \ub118\uaca8\uc904 \ub54c\ub294 \uc784\uc2dc \ubcc0\uc218\uc778 dateValue\ub97c \uc81c\uc678\ud558\uace0 \uc804\ub2ec \uac00\ub2a5\ud569\ub2c8\ub2e4.\n    \/\/ When passing data to the frontend, you can exclude the temporary variable dateValue.\n    const sortedPosts = allPostsData.map(({ slug, content }) => ({ slug, content }));\n\n    return NextResponse.json(sortedPosts);\n\n  } catch (error) {\n    console.error(\"\ud30c\uc77c \uc77d\uae30 \ubc0f \uc815\ub82c \uc624\ub958 \/ File reading and sorting error:\", error);\n    return NextResponse.json({ message: \"\ub370\uc774\ud130\ub97c \uc77d\uc5b4\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4\/Failed to read data.\" }, { status: 500 });\n  }\n}<\/code><\/pre>\n\n\n\n<p>1)\uc5ec\uae30\ub294 md\ud30c\uc77c\uc5d0\uc11c \uae00\uc744 \ubd88\ub7ec\uc624\ub294 \ubd80\ubd84\uc785\ub2c8\ub2e4.<br>This is the section where content is loaded from an MD file.<\/p>\n\n\n\n<p>2)app\/posts\ub514\ub809\ud1a0\ub9ac\uc5d0\uc11c fs.readdirSync\ub85c md\ud30c\uc77c\uc758 \ub0b4\uc6a9\uc744 \ud638\ucd9c\ud569\ub2c8\ub2e4.<br>Retrieve the contents of the .md files using <code>fs.readdirSync<\/code> in the <code>app\/posts<\/code> directory.<\/p>\n\n\n\n<p>3)\ud30c\uc77c\uc758 \ub0b4\uc6a9\uc744 \uc815\ub82c\ud574\uc11c json\ub370\uc774\ud130\ub85c \ub9ac\ud134\ud569\ub2c8\ub2e4.<br>Sorts the file contents and returns them as JSON data.<\/p>\n\n\n\n<p>&#8212; POST\ud568\uc218 \/ POST function<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export async function POST(request) {\n  try {\n    const body = await request.json();\n    \/\/ \ube14\ub85c\uadf8\uc5d0 \ub9de\uac8c name \ub300\uc2e0 title(\uc81c\ubaa9), content(\ubcf8\ubb38)\ub97c \ubc1b\uc2b5\ub2c8\ub2e4.\n    \/\/ Instead of name, we receive title and content to fit the blog context.\n    const { title, content } = body;\n\n    if (!title || !content) {\n      return NextResponse.json(\n        { message: '\uc81c\ubaa9\uacfc \ub0b4\uc6a9\uc744 \ubaa8\ub450 \uc785\ub825\ud574\uc8fc\uc138\uc694.  \/ Please enter both title and content.' }, \n        { status: 400 }\n      );\n    }\n\n    \/\/ slug\ub9cc\ub4e4\uae30,\uacf5\ubc31\uc744 \ud558\uc774\ud508\uc73c\ub85c \ub300\uccb4\ud558\uace0 \ud2b9\uc218\ubb38\uc790 \uc81c\uac70\n    \/\/ Create a slug by replacing spaces with hyphens and removing special characters \n    const slug = title\n      .trim()\n      .replace(\/\\s+\/g, '-')\n      .replace(\/&#91;^a-zA-Z0-9\uac00-\ud7a3\\-]\/g, '');\n\n    \/\/ \uc800\uc7a5\ud560 \ud30c\uc77c \uacbd\ub85c \uc9c0\uc815 (app\/posts\/\ud30c\uc77c\uba85.md)\n    \/\/ Specify the file path to save (app\/posts\/filename.md)\n    const postsDirectory = path.join(process.cwd(), 'app', 'posts');\n    \n    const fullPath = path.join(postsDirectory, `${slug}.md`);\n\n    \/\/ \uc548\uc804\uc7a5\uce58: \ud639\uc2dc \ub611\uac19\uc740 \uc81c\ubaa9\uc758 \ud30c\uc77c\uc774 \uc774\ubbf8 \uc874\uc7ac\ud558\uba74 \ucda9\ub3cc \ubc29\uc9c0\n    \/\/ Safety measure: Prevent collision if a file with the same title already exists\n    if (fs.existsSync(fullPath)) {\n      return NextResponse.json(\n        { message: '\uc774\ubbf8 \ub3d9\uc77c\ud55c \uc81c\ubaa9\uc758 \uae00\uc774 \uc874\uc7ac\ud569\ub2c8\ub2e4. \/ A post with the same title already exists.' }, \n        { status: 409 }\n      );\n    }\n\n    \/\/ \ub9c8\ud06c\ub2e4\uc6b4 \ud30c\uc77c\uc5d0 \ub4e4\uc5b4\uac08 \ud3ec\ub9f7(Front-matter \ud3ec\ud568) \uad6c\uc131\ud558\uae30\n    \/\/ Construct the format (including front-matter) to be included in the markdown file\n    const fileContent = `---\ntitle: \"${title}\"\ndate: \"${new Date().toISOString().split('T')&#91;0]}\"\n---\n\n${content}`;\n\n    \/\/ app\/posts \ud3f4\ub354 \uc548\uc5d0 \ud30c\uc77c \uc0dd\uc131 \ubc0f \uc4f0\uae30!\n    \/\/ Create and write the file inside the app\/posts folder!\n    fs.writeFileSync(fullPath, fileContent, 'utf8');\n\n    \/\/ \uc131\uacf5 \uc2dc \ube0c\ub77c\uc6b0\uc800(\ud504\ub860\ud2b8\uc5d4\ub4dc)\ub85c \ub358\uc838\uc904 \uacb0\uacfc\uac12\n    \/\/ he result to be sent to the browser (frontend) upon success\n    return NextResponse.json({ slug, content: fileContent }, { status: 201 });\n\n  } catch (error) {\n    console.error(\"\ub9c8\ud06c\ub2e4\uc6b4 \ud30c\uc77c \uc0dd\uc131 \uc624\ub958\/Markdown file creation error:\", error);\n    return NextResponse.json(\n      { message: '\uc11c\ubc84\uc5d0\uc11c \ud30c\uc77c\uc744 \uc0dd\uc131\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \/ Failed to create markdown file.' }, \n      { status: 500 }\n    );\n  }\n}<\/code><\/pre>\n\n\n\n<p>1)\uae00\uc744 md\ud30c\uc77c\uc5d0 \uc800\uc7a5\ud558\ub294 \ubd80\ubd84\uc785\ub2c8\ub2e4.<br>This is the part where the text is saved to an MD file.<\/p>\n\n\n\n<p>2)\ud3ec\uc2a4\ud2b8\ub9c8\ub2e4 md\ud30c\uc77c\uc744 \ub9cc\ub4e4\uace0 \uae00 \ub0b4\uc6a9\uc744 md\ud30c\uc77c\uc5d0 \uc800\uc7a5\ud569\ub2c8\ub2e4.<br>For each post, create an MD file and save the content of the post in it.<\/p>\n\n\n\n<p>\u2714\ufe0f page.tsx<\/p>\n\n\n\n<p>&#8212; name\uc744 \uc5c6\uc560\uace0 title\ub85c \ubc14\uafe8\uae30 \ub54c\ubb38\uc5d0 \uad00\ub828\ub41c \ucf54\ub4dc\ub294 \ubaa8\ub450 title\ub85c \ubc14\uafc9\ub2c8\ub2e4.<br>Since <code>name<\/code> has been removed and replaced with <code>title<\/code>, update all related code to use <code>title<\/code>.<\/p>\n\n\n\n<p>&#8212; slug\ub294 \ud0c0\uc785\uc774 \uc5c6\uc73c\uba74 \uc5d0\ub514\ud130\uc5d0\uc11c \uc624\ub958\ub85c \ub098\ud0c0\ub098\uae30 \ub54c\ubb38\uc5d0 \ud0c0\uc785\uc744 \uc9c0\uc815\ud574\uc90d\ub2c8\ub2e4.<br>Since an error occurs in the editor if the slug lacks a type, we specify a type for it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>interface GuestbookItem {\n  id: number;\n  <strong>title: string;<\/strong>\n  <strong>slug: string;<\/strong>\n  content: string;\n}\n\n... \uc911\ub7b5 \/ omitted ...\n\n  const &#91;posts, setPosts] = useState&lt;GuestbookItem&#91;]>(&#91;]);\n  <strong>const &#91;title, setTitle] = useState(\"\");<\/strong>\n  const &#91;content, setContent] = useState(\"\");\n  const &#91;isLoading, setIsLoading] = useState(true);\n\n... \uc911\ub7b5 \/ omitted ...\n\n    if (!<strong>title.trim<\/strong>() || !content.trim()) {\n      alert(\"\uc81c\ubaa9\uacfc \ub0b4\uc6a9\uc744 \ubaa8\ub450 \uc785\ub825\ud574 \uc8fc\uc138\uc694.\/Please enter both title and content.\");\n      return;\n    }\n\n... \uc911\ub7b5 \/ omitted ...\n\n      if (response.ok) {\n        <strong>setTitle(\"\");<\/strong>\n        setContent(\"\");\n        fetchPosts();\n      } else {\n        alert(\"\ub4f1\ub85d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.\/Failed to register the entry.\");\n      }<\/code><\/pre>\n\n\n\n<p>&#8212; \ud0c0\uc774\ud2c0\uc774\ub098 \ub0b4\uc6a9\uc774 \ud654\uba74\uc5d0 \ucd9c\ub825\uac00\ub2a5\ud558\ub3c4\ub85d \uc544\ub798\ucc98\ub7fc \ucf54\ub4dc\ub97c \ubc14\uafc9\ub2c8\ub2e4.<br>Modify the code as shown below so that the title or content can be displayed on the screen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>      &lt;h2 className=\"text-2xl font-bold text-gray-800\">\ucd5c\uadfc \ubc29\uba85\ub85d \ubaa9\ub85d \/ Recent Guestbook Entries&lt;\/h2>\n      {isLoading ? (\n        &lt;p>\ub85c\ub529 \uc911\/Loading...&lt;\/p>\n      ) : posts.length === 0 ? (\n        &lt;p>\uc791\uc131\ub41c \ubc29\uba85\ub85d\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uccab \uae00\uc744 \ub0a8\uaca8\ubcf4\uc138\uc694! \/ There are no guestbook entries yet. Be the first to leave a message!&lt;\/p>\n      ) : (\n        &lt;ul style={{ listStyle: \"none\", padding: 0 }}>\n          {posts.map((post) => (\n            &lt;li \n              key={post.slug} \n              style={{ \n                border: \"1px solid #f0f0f0\", \n                borderRadius: \"8px\", \n                padding: \"20px\",\n                backgroundColor: \"#fff\",\n                boxShadow: \"0 2px 4px rgba(0,0,0,0.02)\"\n              }}\n            >\n              {\/* \ud30c\uc77c \uc774\ub984\uc5d0\uc11c \ub300\ubb38\uc790\ud654 \ubc0f \ud558\uc774\ud508 \uc81c\uac70 \/ Convert file name to a more readable title format (capitalize and remove hyphens) *\/}\n              &lt;strong className=\"text-xl text-gray-900 block capitalize mb-2\">\n                {post.slug.replace(\/-\/g, \" \")}\n              &lt;\/strong>\n              \n              {\/* \ub9c8\ud06c\ub2e4\uc6b4 \uc6d0\ubcf8 \ud14d\uc2a4\ud2b8 \ub0b4\uc6a9 \uc77c\ubd80 \ucd9c\ub825 \/ Display a portion of the original markdown text *\/}\n              &lt;div \n                style={{ \n                  margin: \"10px 0 0 0\", \n                  color: \"#555\", \n                  whiteSpace: \"pre-wrap\",\n                  fontSize: \"14px\",\n                  lineHeight: \"1.6\",\n                  backgroundColor: \"#fafafa\",\n                  padding: \"12px\",\n                  borderRadius: \"6px\"\n                }}\n              >\n                {post.content}\n              &lt;\/div>\n            &lt;\/li>\n          ))}\n        &lt;\/ul>\n      )}<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 Nextjs \uc11c\ubc84 \uc2e4\ud589 \/ Run Nextjs Server<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm run dev<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ube0c\ub77c\uc6b0\uc800 \uc811\uc18d http:\/\/localhost:3000<br> Access http:\/\/localhost:3000 in your browser.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"795\" height=\"1024\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/guestbook-jpg-795x1024.jpg\" alt=\"\" class=\"wp-image-7302\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/guestbook-jpg-795x1024.jpg 795w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/guestbook-jpg-233x300.jpg 233w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/guestbook-jpg-768x990.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/guestbook-jpg-400x516.jpg 400w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/08\/guestbook-jpg.jpg 800w\" sizes=\"auto, (max-width: 795px) 100vw, 795px\" \/><figcaption class=\"wp-element-caption\">Screenshot<\/figcaption><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb \uc774\uc804 \ud3ec\uc2a4\ud2b8\uc758 \uae30\uc874 \ubc29\uba85\ub85d\uc5d0\uc11c \ub370\uc774\ud130\ub97c md(markdown)\ud30c\uc77c\uc5d0 \uc800\uc7a5\ud569\ub2c8\ub2e4.Data from the existing guestbook in the previous post is saved to an MD (Markdown) file. \ud83d\udc49\ud83c\udffb \uc5ec\uae30\uc11c\ub294 \uc571 \ub77c\uc6b0\ud2b8 \ubc29\uc2dd\uc744 \uc0ac\uc6a9\ud569\ub2c8\ub2e4.Here, the App Router approach is used. \ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc124\uce58 \/ Project Installation \ud83d\udcc1 \ucf54\ub4dc \uc218\uc815 \/ Code modification \u2714\ufe0f route.js &#8212; GET\ud568\uc218 \/ GET function 1)\uc5ec\uae30\ub294 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[27,1,7],"tags":[],"class_list":["post-7266","post","type-post","status-publish","format-standard","hentry","category-nextjs","category-uncategorized","category-website","missing-thumbnail"],"_links":{"self":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/7266","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=7266"}],"version-history":[{"count":33,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/7266\/revisions"}],"predecessor-version":[{"id":7303,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/7266\/revisions\/7303"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=7266"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=7266"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=7266"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}