[nextjs]SNS Server-32(myapp42) 

👉🏻 myapp42에서는 존재하지 않는 계정으로 sns페이지 접근시 404페이지로 안내합니다.
In myapp42, users are directed to a 404 page when attempting to access an SNS page for a non-existent account.

👉🏻 팔로워 목록,팔로잉 목록을 구현합니다.
Implement the follower and following lists.

👉🏻 전체 코드는 깃허브에서 확인 할 수 있습니다.
You can find the full code on GitHub.

https://github.com/gideonslife01/flm-nextjs

📁 전체 프로젝트 구조 / Overall Project Structure

myapp project/  
├── app/  (Next.js App Router)
│   ├── .well-known/webfinger/route.ts  -> webfinger
│   ├── api/follow/route.ts    -> Follow API(temporary)
│   ├── api/announce/route.ts  -> Boost(Announcement)
│   ├── api/like/route.ts.     -> Like API
│   ├── api/posts/route.ts     -> Writing API
│   ├── api/timeline/route.ts  -> Timeline API
│   ├── api/v1/instance/route.ts -> auth
│   ├── api/v1/apps/route.ts.    -> auth
│   ├── api/v1/accounts/verify_credentials/route.ts -> auth
│   ├── api/v1/statuses/route.ts -> Write Post
│   ├── api/v1/timelines/home/route.ts -> timeline
│   ├── oauth/authorize/route.ts -> auth
│   ├── oauth/token/route.ts -> auth
│   ├── api/v1/search/home/route.ts -> search
│   ├── api/v2/search/home/route.ts -> search
│   ├── api/v1/accounts/[id]/followers/route.ts -> followers
│   ├── api/v1/accounts/[id]/following/route.ts -> following
│   ├── api/auth/signup/route.ts        -> Signup
│   ├── api/auth/login/route.ts         -> Login
│   ├── api/auth/logout/route.ts        -> Logout
│   ├── api/auth/refresh/route.ts       -> Refresh Token
│   ├── api/auth/me/route.tsx           -> Login Check
│   ├── auth/signup/page.tsx            -> Signup UI
│   ├── auth/signup/page.tsx            -> Login UI
│   ├── users/[username]/
│   │   ├── statuses/[id]/route.ts -> Indivisual Post
│   │   ├── route.ts               -> Acotr Information
│   │   ├── followers/route.ts     -> Followers List
│   │   ├── following/route.ts     -> Following List
│   │   ├── inbox/route.ts         -> Inbox
│   │   └── outbox/route.ts        -> outbox
│   ├── usersui/[username]/
│   │   ├── page.tsx               -> Timeline UI
│   │   ├── followers/page.tsx.      -> Followers list
│   │   ├── followers/ClientList.tsx -> Followers,Following list UI
│   │   ├── following/page.tsx       -> Following list 
│   │   └── _components/themes/
│   │       ├── themeex/ThemeexTheme.tsx   -> Example Theme
│   │       ├── pinafore/PinaforeTheme.tsx -> Theme 1
│   │       ├── mastodon/MastodonTheme.tsx -> Theme 2
│   │       └── minimal/MinimaltTheme.tsx  -> Theme 3
│   ├── layout.tsx, page.tsx, globals.css
│   └── favicon.ico
├── lib/
│   ├── theme.tsx              -> Theme Provider
│   ├── watchThemes.ts         -> Check real-time theme changes
│   ├── auth.ts                -> Authentication, User Management
│   ├── ap.ts                  -> Follow,Undo,Create,Likes,Announce
│   └── db.ts                  -> DB connection
├── data/
│   ├── keys/userIDs/          -> private.pem, public.pem(New)
│   └── keys/                  -> private.pem, public.pem(legacy)
├── data.sqlite                -> Database(1/3)
├── data.sqlite-wal            -> Database(2/3)
├── data.sqlite-shm            -> Database(3/3)
├── Caddyfile                  -> https 
├── instrumentation.ts         -> Background Server
└── package.json

📁 프로젝트 시작 / Project Start


📁 코드수정 / Code Modification

✔️ 가입되지 않는 페이지로 접근시 404 not found 메세지 안내

— https://aloy-horizon.duckdns.org/@user5 처럼 존재하지 않는 계정으로 접근시 지금은 빈 데이터 페이지를 보여줍니다.
Currently, accessing a non-existent account—such as https://aloy-horizon.duckdns.org/@user5—displays a page with empty data.

— 존재하지 않는 계정으로 SNS 페이지를 접근시 404 not found 페이지로 안내 하는 설정입니다.
This setting redirects users to a “404 Not Found” page when they attempt to access an SNS page for a non-existent account.

1)page.tsx(server part)를 새로 만들고 기존의 page.tsx를 ClientPage.tsx로 이름 바꾸고 코드 수정합니다.
Create a new page.tsx (server component), rename the existing page.tsx to ClientPage.tsx, and modify the code.

A.app/usersui/[username]/page.tsx

// ✅ app/usersui/[username]/page.tsx - only server

import { notFound } from 'next/navigation';
import db from '@/lib/db';
import ClientPage from './ClientPage'; 

// daynamic과 runtime은 nextjs가 읽는 값 / `dynamic` and `runtime` are values ​​read by Next.js.
// force-dynamic : 캐시 적용하지 않음 / Do not use cache
// runtime : DB사용하기 위한 설정 / Configuration for Using the Database

export const dynamic = 'force-dynamic'; // no cache
export const runtime = 'nodejs'; // fot db

type Props = {
  params: Promise<{ username: string }>;
};

export default async function Page({ params }: Props) {
  const { username } = await params;

  // DB 체크 / Check DB
  const user = db.prepare('SELECT username FROM users WHERE username = ?').get(username) as { username: string } | undefined;

  // 404
  if (!user) {
    notFound(); 
  }

  // - 이전의 page.tsx였던 ClientPage.tsx파일 불러오기 , 기존의 params없애고 username 직접 입력
  // call client
  return <ClientPage username={username} />;
}

B.app/usersui/[username]/ClientPage.tsx(이름 변경 / Change Name)

a.기존의 params부분을 지우고 파라메터로 입력된 username 변수 처리 형태로 바꿉니다.
Remove the existing params section and modify it to handle the username variable passed as a parameter.

... ...
export default function ClientPage({ username }: { username: string }) {
... ...
  // ✅ myapp42 - delete params then
  useEffect(() => {
    if (!username) return;
    setLoading(true);
    fetch(`/api/timeline?username=${username}`, { cache: 'no-store' })
     .then(r => r.json())
     .then(data => {
        console.log('📦 timeline', data[0]);
        setTimeline(data);
        setLoading(false);
      })
     .catch(() => setLoading(false));
  }, [username]); 
... ...
}
... ...

📁 라우트 추가 / Add route

✔️ follow,follwers 리스트 부분입니다.
This is the section for the following and followers list.

–먼저 next.config.ts에서 @user/following @user/follwers로 접근시 usersui/username/folloing 또는 followers로 접근하도록 설정합니다.
First, configure next.config.ts so that accessing @user/following or @user/followers routes to usersui/username/following or usersui/username/followers.

1)next.config.ts

import type { NextConfig } from "next";

const nextConfig: NextConfig = {

  /* allow domain*/
  allowedDevOrigins: ['aloy-horizon.duckdns.org', '*.duckdns.org'],

  /* host.domain.org/@userid */
  async rewrites() {
    return [
      {
        source: '/@:username',
        destination: '/usersui/:username',
      },
      {
        source: '/@:username/following',
        destination: '/usersui/:username/following',
      },
      {
        source: '/@:username/followers',
        destination: '/usersui/:username/followers',
      },
    ]
  },
  
};

export default nextConfig;

— app/usersui/[username]/followers/page.tsx

... ...
export default async function Page({ params }: { params: Promise<{ username: string }> }) {
... ...
  if (localUsernames.length > 0) {
    const placeholders = localUsernames.map(() => '?').join(',');
    // ✅ FIX! acct 빼고 display_name만!
    const users = db.prepare(`SELECT username, display_name FROM users WHERE username IN (${placeholders})`).all(...localUsernames) as any[];
    users.forEach((u: any) => localUsersMap.set(u.username, u));
  }
... ...
 return <FollowList username={username} type="followers" users={users} />;
}
... ...

1)username을 데이터 베이스에서 검색
Search for username in the database

2)FollowList 페이지 호출하고 파라메터로 username,users,type 전달
Call the FollowList page and pass username, users, and type as parameters.

— app/usersui/[username]/followers/ClientList.tsx

... ...
export default function FollowList({ username, type, users }: { username: string, type: 'followers' | 'following', users: any[] }) {

... ...
       {users.map((u: any) => (
          <Link key={u.username} href={`/usersui/${u.username}`} style={{ textDecoration: 'none', color: 'inherit' }}>
            <div className="follow-row">
              {u.avatar? <img src={u.avatar} style={{ width: 46, height: 46, borderRadius: '50%' }} alt="" /> : <Avatar username={u.username} />}
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 700 }}>{u.display_name || u.username}</div>
                <div style={{ fontSize: 13, color: '#657786' }}>@{u.acct || u.username}</div>
                {u.note && <div style={{ fontSize: 13, marginTop: 4, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }} dangerouslySetInnerHTML={{ __html: u.note }} />}
              </div>
            </div>
          </Link>
        ))}
... ...

}

1)실제 화면에 표시되는 UI
UI displayed on the actual screen

2)전달 받은 데이트를 화면표시함.
Displays the received date on the screen.

— app/usersui/[username]/following/page.tsx

... ...
import FollowList from '../followers/ClientList';
... ...
export default async function Page({ params }: { params: Promise<{ username: string }> }) {
... ...
  if (localNames.length > 0) {
    const ph = localNames.map(() => '?').join(',');
    const users = db.prepare(`SELECT username, display_name FROM users WHERE username IN (${ph})`).all(...localNames) as any[];
    users.forEach(u => map.set(u.username, u));
  }
... ...
return <FollowList username={username} type="following" users={users} />;
}

1)username을 데이터 베이스에서 검색
Search for username in the database

2)FollowList 페이지 호출하고 파라메터로 username,users,type 전달
Call the FollowList page and pass username, users, and type as parameters.

3)UI는 followers/ClientList.tsx 파일을 재사용.
The UI reuses the followers/ClientList.tsx file.

📁 테스트 / Test

✔️ 존재하지 않는 계정으로 sns페이지 접근할경우
When accessing a social media page for a non-existent account

— 404 적용 전 / 404 Before Application

404 처리 이전 / Before 404 handling

— 404 적용 후 / After applying 404

404 처리 이후 / After 404 handling

💡 팔로우 리스트, 팔로워리스트 부분은 다음 프로젝트에서 구조 변경 될 수 있습니다.
The structure of the following and follower lists may be modified in the next project.

✔️ 팔로우리스트 / Follow list

Follow list

✔️ 팔로워리스트 / Follower list

Followers list

요한복음 8장 32절 / John 8:32

“그리고 너희는 진리를 알게 될 것이며, 진리가 너희를 자유롭게 할 것이다.”

“Then you will know the truth ,and the truth will set you free”

Leave a Reply