ππ» myapp26μλ μ€ν¨κΈ°λ₯μ ꡬνν©λλ€.
Skin functionality is implemented in myapp26.
ππ» μ½λ μμ΄ λ§μμ μ£Όμ λΆλΆλ§ μ€λͺ
ν©λλ€ .
Due to the large volume of code, I will explain only the key parts.
ππ» μ 체μ½λλ κΉνλΈ μ½λλ₯Ό μ°Έμ‘°νμΈμ
Please refer to the GitHub repository for the full code.
https://github.com/gideonslife01/flm-nextjs
π μ 체 νλ‘μ νΈ κ΅¬μ‘° / Overall Project Structure
myapp26/
βββ 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
β βββ 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
β β βββ _components/themes/
β β βββ pinafore/Pinaforetheme.tsx -> Theme 1
β β βββ mastodon/Mastodontheme.tsx -> Theme 2
β β βββ minimal/Minimaltheme.tsx -> Theme 3
β βββ layout.tsx, page.tsx, globals.css
β βββ favicon.ico
βββ lib/
β βββ theme.tsx -> Theme Provider
β βββ ap.ts -> Follow Accept
β βββ db.ts -> DB connection
βββ data/
β βββ keys/ -> private.pem, public.pem
βββ data.sqlite -> Database
βββ Caddyfile -> https
βββ package.json
π νλ‘μ νΈ μμ(myapp26)
Project Start (myapp26)
npx create-next-app@latest
π SQLiteμ€μΉ / Installing SQLite
cd ~/myapp26
npm install better-sqlite3
npm install -D @types/better-sqlite3
π DDNS,httpsμ€μ / DDNS,https settings
π λλ©μΈνμ©, @userid μ¬μ©νκΈ°
Allowing domains, using @userid
— 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',
},
]
}
};
export default nextConfig;
π μ½λμμ / Code Modification
βοΈ myapp26/lib/theme.tsxνμΌμ λΆλ¬μ 컨ν
μ€νΈ νλ‘λ°μ΄λλ₯Ό μ μ©ν©λλ€.
Import the myapp26/lib/theme.tsx file and apply the context provider.
π‘ 컨ν
μ€νΈ νλ‘λ°μ΄λλ νλμ μ΄λ²€νΈλ‘ μ 체 μνλ₯Ό λ³κ²½νλ κΈ°λ₯μΌλ‘ ν
λ§μ μ©μ΄λ μΈμ΄ μ μ© μ€μ μ λ§μ΄ μ¬μ©λ©λλ€.
A Context Provider is a feature that allows the entire state to be updated via a single event; it is commonly used for settings such as applying themes or languages.
— myapp26/app/usersui/[username]/Layout.tsx
import { ThemeProvider } from "@/lib/theme"; // β
myapp26
... ...
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
<html
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">
{/* β
myapp26 - Add ThemeProvide */}
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
);
}
βοΈ μ¬κΈ°μ ν
λ§νμΌμ λΆλ¬μ€κ³ ν
λ§(μ€ν¨)μμ μ©ν©λλ€.
Here, you load the theme file and apply the theme (skin).
— myapp26/app/paget.tsx
... ...
import { PinaforeTheme } from './_components/themes/pinafore/Pinaforetheme';
import { MastodonTheme } from './_components/themes/mastodon/Mastodontheme';
import { MinimalTheme } from './_components/themes/minimal/Minimaltheme';
... ...
// β
timeline fetch
useEffect(() => {
params.then(p => {
setUsername(p.username);
fetch(`/api/timeline?username=${p.username}`)
.then(r => r.json())
.then(data => {
setTimeline(data);
setLoading(false);
});
});
}, [params]);
... ...
if (loading) return <div style={{ padding: 20 }}>Loading...</div>;
// β
theme λΆκΈ° + props μ λ¬ / Theme branching + props passing
const props = { timeline, username, onBoost: handleBoost, onLike: handleLike };
// apply theme
if (theme === 'pinafore') return <PinaforeTheme {...props} />
if (theme === 'mastodon') return <MastodonTheme {...props} />
if (theme === 'minimal') return <MinimalTheme {...props} />
return <PinaforeTheme {...props} />;
π λλ ν 리 λ° νμΌ μΆκ° / Add directories and files
βοΈ νμλΌμΈ μ€ν¨(ν λ§) UI / Timeline Skin (Theme) UI
— μλμ νμΌμ λͺ¨λ ν
λ§ λμμΈμΌλ‘ μ’μμ,λΆμ€νΈκΈ°λ₯μ λΉ μ Έμμ΅λλ€.
The files below are all theme designs; the boost function is not included.
myapp26/app/usersui/[username]/_components/themes/pinafore/Pinaforetheme.tsx
myapp26/app/usersui/[username]/_components/themes/mastodon/Mastodontheme.tsx
myapp26/app/usersui/[username]/_components/themes/minimal/Minimaltheme.tsx
— μΈ νμΌ λͺ¨λ λμΌνλ©° ν
λ§μ ꡬλΆμ΄ κ°λ₯νκ² ν
λ§ μ΄λ¦λ§ νμ쀬μ΅λλ€.
All three files are identical; only the theme names are displayed to distinguish between them.
<h2>π {username}/ Pinafore</h2> {/* ν
λ§μ΄λ¦ / Theme Name */}


— ν
λ§ λ³μμ κ°μ΄ λ³νλ©΄ 컨ν
μ€νΈ νλ‘λ°μ΄λμμ λ‘컬 μ€ν 리μ§μ κ°μ λ³κ²½ν©λλ€.
When the value of the theme variable changes, the context provider updates the value in local storage.
... ...
const [theme, setTheme] = useState<ThemeName>('pinafore');
... ...
<select
value={theme} // νμ¬ λ‘컬μ€ν λ¦¬μ§ κ° νμ / Display current local storage value
onChange={(e) => {
setTheme(e.target.value as any);
}}>
<option value="pinafore">Pinafore</option>
<option value="mastodon">Mastodon</option>
<option value="minimal">Minimal</option>
</select>


βοΈ μ»¨ν μ€νΈ νλ‘λ°μ΄λ / Context Provider
— myapp26/lib/theme.tsx
... ...
useEffect(() => {
const saved = localStorage.getItem('theme') as ThemeName;
if (saved) setTheme(saved);
}, []);
... ...
return (
<ThemeContext.Provider value={{ theme, setTheme: updateTheme }}>
<div data-theme={theme} style={{ minHeight: '100%', display: 'flex', flexDirection: 'column', flex: 1 }}>
{children}
</div>
</ThemeContext.Provider>
);
1)컨ν
μ€νΈ νλ‘λ°μ΄λμ themeλ³μλ₯Ό νλΌλ©ν°λ‘ μ μ©ν©λλ€.
Apply the theme variable as a parameter to the context provider.
2)theme κ°μ΄ λ³κ²½λλ©΄ λ€λ₯Έ ν
λ§νμΌμ λΆλ¬μ€κΈ° μν΄μ μ¬μ©λ©λλ€.
It is used to load a different theme file when the theme value changes.
3)ν
λ§μ΄λ¦μ λΈλΌμ°μ μ λ‘컬 μ€ν 리μ§μ μ μ₯λ©λλ€.
The theme name is stored in the browser’s local storage.
μν볡μ 8μ₯ 32μ / John 8:32
“κ·Έλ¦¬κ³ λν¬λ μ§λ¦¬λ₯Ό μκ² λ κ²μ΄λ©°, μ§λ¦¬κ° λν¬λ₯Ό μμ λ‘κ² ν κ²μ΄λ€.”
“Then you will know the truth ,and the truth will set you free”