μΉμ¬μ΄νΈΒ Β Β Β Β
νλ μ΄μ€ν μ΄Β Β Β Β Β
React Native GitHubΒ Β Β Β Β
React GitHub (Migration μ )
νλ‘μ νΈ κΈ°κ°: 2023.09.29 ~ 2023.11.16 | 리ν©ν λ§ κΈ°κ°: 2023.11.20 ~ μ§ν μ€
체νκ³μ ID: [email protected] | PW: 123qwe
- π¨βπ»π©βπ» ν μκ°
- π οΈ κΈ°μ λ° κ°λ° νκ²½
- π‘ μ£Όμ κΈ°λ₯
- π ν΅μ¬κΈ°μ
- π νΈλ¬λΈ μν
- πββοΈ μ μ νΌλλ°±
- π λ²μ 2(λ§μ΄κ·Έλ μ΄μ , κΈ°λ₯μΆκ°)
- π₯ Firebase ꡬ쑰
- βοΈ Best Practices
- π νλ‘μ νΈ κ΄λ ¨ λ¬Έμ
- μ€νλμ
- λ‘κ·ΈμΈ λ° νμκ°μ
- λμ μ¨λ²/곡μ μ¨λ² 리μ€νΈ
- μ¨λ² μ λ ¬(μ΅μ μ/μ€λλμ)
- μ¨λ² μΆκ°/μμ /μμ
- μ¨λ² 곡μ
- μ¨λ²μ νΌλ μ¨λ²ν
- μ¨λ²μ μ¬μ§ μΆκ°
- νΌλ μμ
- μ¨λ²μ νΌλ 리μ€νΈν
- νΌλ μμ /μμ /μ¨λ² λ³κ²½
- μ¬μ§ λ° μ λͺ© (νμ)
- λ³Έλ¬Έ, λ μ¨/κΈ°λΆ μ΄λͺ¨ν°μ½, μμΉ (μ ν)
- νλ‘ν μμ
- νμνν΄ λ° λ‘κ·Έμμ
- μ΄μ©μ½κ΄ λ° κ°μΈμ 보μ²λ¦¬λ°©μΉ¨
μ¨λ² 곡μ
- 곡μ ν μ¬μ©μλ₯Ό κ²μν μ μλ€.
- μ¨λ²μ 곡μ νκ±°λ, 곡μ ν λμμ μμ ν μ μλ€.
- νμμ 곡μ νκ±°λ 곡μ λ°μ μ¨λ²μ λ³Ό μ μλ€.
- μ¨λ²μ 곡μ λ°μΌλ©΄, ν΄λΉ μ¨λ²μ μ μ₯λ μ¬μ§μ λ³Ό μ μλ€.
-
μ¬μ©μ κ²μ
Firebase Admin SDKλ₯Ό μ¬μ©νμ¬, μ¬μ©μλ₯Ό λΆλ¬μ¨λ€.
// src/app/api/user/route.ts adminApp.auth().getUserByEmail(email);
-
곡μ /곡μ μ·¨μ
Firestore Databaseμ 곡μ μ 보 μ μ₯ & μμ
// [uid]/[uid] sharedAlbums: Reference(albumDoc)[] // [uid]/[uid]/album/[albumId] sharedUsers: {uid, permission}[]
-
ν - 곡μ μ¨λ²
-
Firestoreμμ λ‘κ·ΈμΈν μ¬μ©μμ 곡μ μ¨λ² 리μ€νΈλ₯Ό κ°μ Έμ¨λ€.
// src/utils/SDKUtils.ts const getSharedAlbums = async ( uid: string, ): Promise<DocumentReference[]> => { const userDocRef = doc(appFireStore, uid, uid); const userDoc = (await getDoc(userDocRef)).data(); return userDoc.sharedAlbums; };
-
곡μ μ¨λ² λ°μ΄ν°λ₯Ό λΆλ¬μ¨λ€.
// src/app/api/album/sharing sharedAlbums.map(async (ref: DocumentReference) => { const albumData = await getDoc(ref).data(); // (μ€λ΅) });
-
곡μ ν μ¬μ©μ λ°μ΄ν°λ₯Ό λΆλ¬μ¨λ€.
// src/app/api/album/sharing const { displayName, email } = await adminAppAuth.getUser(sharedAlbumUserUid);
-
- 곡μ μ¨λ² μμΈ
-
νΌλ 리μ€νΈλ₯Ό μ»κΈ° μν΄ κ³΅μ μ¨λ²/λμ μ¨λ² ꡬλΆμμ΄ μμ²μ 보λΈλ€.
// src/services/feed.ts // Path Parameter(uid, albumName)λ₯Ό 쿼리 맀κ°λ³μλ‘ μμ²μ μΆκ°νμ¬ μ μ‘ // μ¨λ² μμΈνμ΄μ§ κ²½λ‘: {uid}/album/{albumName} // νΌλ μμΈνμ΄μ§ κ²½λ‘: {uid}/album/{albumName}/feed await fetch( `${API_URL}/feed?limit=${limit}&skip=${skip}&album=${albumName}&uid=${uid}`, );
-
μΏ ν€μ uid(λ‘κ·ΈμΈν μ¬μ©μ)μ 쿼리 맀κ°λ³μλ‘ λ°μ uid(μ¨λ² μμ±μ)κ° λ€λ₯Ό κ²½μ° κΆνμ κ²μ¬νλ€.
// src/app/api/route.ts export async function GET(req: NextRequest) { // μ€λ΅ let hasPermission = true; if (userUid !== uid) { const sharedAlbums = await getSharedAlbums(userUid); hasPermission = await checkAlbumPermission(albumDoc, sharedAlbums); } if (!hasPermission) { return new Response('μ κ·Ό κΆνμ΄ μλ μ¨λ²μ λλ€.', { status: 403, }); } // μ€λ΅ }
-
Masonry Layout
-
CSS
-
λΆλͺ¨ μμ CSS
// src/containers/albumDetail/StyledFeed.ts const StyledFeedList = styled.ul` display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: -8px -8px; grid-auto-rows: 1px; `;
-
μμ΄ν CSS
// src/components/AlbumItem/StyledAlbumItem.ts const StyledAlbumItem = styled.li` margin: 8px; `;
-
-
JS
-
gridRowEnd κ°μ κ³μ°νλ 컀μ€ν ν
// src/hooks/useAlbumItemLayout.ts interface ImgSize { width: number; height: number; } function useAlbumItemLayout(node: HTMLLIElement) { const [imgSize, setImgSize] = useState<ImgSize | null>(null); const [gridRowEnd, setGridRowEnd] = useState(''); useEffect(() => { const setLayout = async () => { if (!imgSize || !node) { return; } const height = node.clientWidth * (imgSize.height / imgSize.width); setGridRowEnd(`span ${Math.round(height + 16)}`); }; setLayout(); }, [imgSize]); return { setImgSize, gridRowEnd }; }
-
gridRowEnd κ°μ κ³μ°νκΈ° μν΄ νμν μμ΄ν μ΄λ―Έμ§ μ¬μ΄μ¦ ꡬνκΈ°
// src/components/AlbumItem/AlbumItem.tsx <img onLoad={(e) => setImgSize({ width: e.currentTarget.naturalWidth, height: e.currentTarget.naturalHeight, }) } />
-
무ν μ€ν¬λ‘€
μ¨λ² μμΈνμ΄μ§μ κ²μλ¬Ό μμΈνμ΄μ§ μ μ©ν κΈ°μ μ λλ€.
λ νμ΄μ§μ μ μ©λλ κΈ°μ λ‘, μ½λ μ€λ³΅μ μ΅μννκΈ° μν΄ Custom Hookμ ν΅ν΄ ν΅μ¬ λ‘μ§μ λΆλ¦¬νμ΅λλ€.
-
Custom Hook
- Intersection Observer APIλ₯Ό νμ©νμ΅λλ€.
- νΉμ μμ΄ν μ΄ λ·°ν¬νΈμ λνλλ©΄ νμ΄μ§λ₯Ό μ λ°μ΄νΈνμ΅λλ€.
// src/hooks/useInfiniteScroll.ts import { useRef, useState } from 'react'; export default function useInfiniteScroll() { const itemRef = useRef<HTMLLIElement | null>(); const observer = useRef<IntersectionObserver | null>(null); const [page, setPage] = useState(1); const observe = (node: HTMLLIElement) => { observer.current = new IntersectionObserver(entries => { if (entries[0].isIntersecting) { setPage(prev => prev + 1); if (observer.current) { observer.current.disconnect(); } } }); observer.current.observe(node); }; const setItemToObserveRef = (node: HTMLLIElement) => { if (node && node !== itemRef.current) { itemRef.current = node; observe(node); } }; return { page, setItemToObserveRef }; }
-
Custom Hook μ¬μ©
// src/containers/albumDetail/albumDetail.tsx const { page, setItemToObserveRef } = useInfiniteScroll(); // page μ λ°μ΄νΈ μ, μΆκ° λ°μ΄ν° νμΉ useEffect(() => { if (page === 1) { return; } (async () => { const feedsToAdd = await getFeeds({ limit: pageSize * page, skip: pageSize * page - pageSize, uid, albumName }); if (feedsToAdd) { setFeedsData(prev => [...prev, ...feedsToAdd]); } })(); }, [page]); // observe item {feedsData.map((v, i) => { return ( <AlbumItem key={v.id} ref={i === feedsData.length - 1 ? setItemToObserveRef : null} /> ); })}
μμ μ»΄ν¬λνΈμμ refλ₯Ό μ λ¬λ°κΈ° μν΄ forwardRef μ¬μ©
// src/components/AlbumItem/AlbumItem.tsx
function AlbumItem(ref: ForwardedRef) { return ( ); }
export default forwardRef(AlbumItem);
```
1) μλλ‘μ΄λ κΈ°κΈ°μ λ€λ‘κ°κΈ°
-
λ€λ‘κ°κΈ° ν΄λ¦ μ, μ±μ΄ λ«νλ μ΄μ
- ν΄κ²°: μ΄μ νμ΄μ§κ° μ‘΄μ¬ν κ²½μ°, μ΄μ νμ΄μ§λ‘ μ΄λνλλ‘ μμ -
λ€λ‘κ°κΈ° ν΄λ¦ μ, κ²μλ¬Ό μ λ‘λ/μμ λͺ¨λ¬μ΄ κ³μ μ΄λ €μλ μ΄μ
- μμΈ: κ²μλ¬Ό μ λ‘λ/μμ λͺ¨λ¬μ΄ νμ΄μ§ μμ μ»΄ν¬λνΈμμ λ λλ§λκΈ° λλ¬Έμ, μ΄μ νμ΄μ§λ‘ μ΄λν΄λ λͺ¨λ¬μ λ«νμ§ μμ
- ν΄κ²°: λͺ¨λ°μΌμμ κ²μλ¬Ό μ λ‘λ/μμ λͺ¨λ¬μ νμ΄μ§λ‘ λ³κ²½ -
κ²μλ¬Ό μ λ‘λ/μμ ν κ²μλ¬Ό μμΈνμ΄μ§μμ λ€λ‘κ°κΈ° ν΄λ¦ μ, κ²μλ¬Ό μ λ‘λ/μμ νμ΄μ§λ‘ λμκ°λ μ΄μ
- μν©: κ²μλ¬Ό μ λ‘λ/μμ νλ©΄μΌλ‘ λμκ°λ νλ¦μ΄ λΆνΈνλ€λ νΌλλ°±μ λ°μ
- ν΄κ²°: λͺ¨λ°μΌμμλ κ²μλ¬Ό μ λ‘λ/μμ μ λͺ¨λ¬λ‘ λλλ¦¬κ³ , λ€λ‘κ°κΈ° ν΄λ¦ μ λͺ¨λ¬μ΄ λ«νλλ‘ λ³κ²½
2) μ΄λ―Έμ§ νμ₯μ μ ν¨μ± κ²μ¬ - SVG
-
λ¬Έμ : μ΄λ―Έμ§ μ ν ν μ ν¨μ± κ²μ¬ μ, svg νμΌμ΄ ν΅κ³Όνμ§ λͺ»νλ λ²κ·Έ
-
μμΈ: κΈ°μ‘΄μ svg νμΌμ image/svgλ‘ κ²μ¬νκ³ μμμΌλ, νμ€ MIME νμ μ image/svg+xmlμ΄κΈ° λλ¬Έ
-
ν΄κ²°: image/svg+xmlμ ν΅κ³Όμν€λλ‘ μ κ· ννμ μμ
/^image\/(jpg|svg|png|jpeg|gif|bmp|tif|heic)$/ // κΈ°μ‘΄ /^image\/(jpg|svg(\+xml)?|png|jpeg|gif|bmp|tif|heic)$/ // λ³κ²½
3) λͺ¨λ¬ λ°°κ²½ μ½ν μΈ μ€ν¬λ‘€
-
λ¬Έμ : λͺ¨λ°μΌμμ κ²μλ¬Ό μ λ‘λ/μμ λͺ¨λ¬ λ΄ μ€ν¬λ‘€ μλ μ, λ°°κ²½ μ½ν μΈ κ° μ€ν¬λ‘€λλ κ²½μ°κ° μμ
-
μμΈ: ν΄λΉ μμμ μ€ν¬λ‘€μ (λ) λ΄λ¦΄/μ¬λ¦΄ μ μλ κ²½μ°, windowμ μ€ν¬λ‘€ μ΄λ²€νΈ λ°μ (chrome λμ λ°©μ)
-
ν΄κ²°: λͺ¨λ°μΌμμ ν΄λΉ λͺ¨λ¬ open μ, bodyμ scroll-rock ν΄λμ€ μΆκ° (close μ, scroll-rock ν΄λμ€ μμ )
.scroll-lock { position: fixed; height: 100vh; overflow: hidden; }
4) λ°μ΄ν° μ λ°μ΄νΈμ UI
- λ¬Έμ : λ°μ΄ν° μ λ°μ΄νΈ μ, λ€λ₯Έ κ²½λ‘λ‘ μ΄λν ν λμμ€λ©΄ μ΄μ λ°μ΄ν°κ° λ λλ§λ¨
- ν΄κ²°: λ°μ΄ν° μ λ°μ΄νΈ μ λΌμ°νΈ μλ‘κ³ μΉ¨
import { useRouter } from 'next/navigation';
router.refresh();
νΌλλ°± λͺ©λ‘
νμκ°μ
μ΄μ©μ½κ΄ μ½κ³ λ€λ‘ μ€λ©΄ μ λ ₯ν΄ λμλ λ΄μ©μ΄ μ¬λΌμ§κ³ , λͺ¨λ μ²΄ν¬ λ°μ€κ° ν΄μ λ¨5a0b0d0νλ‘ν μ¬μ§ λ³κ²½ λ²νΌμ λ§μ°μ€ 컀μλ₯Ό κ°μ Έλ€ λμμ λ, μκ°λ½ λͺ¨μμΌλ‘ λ°λλ©΄ μ’κ² μbdfc9ea- λ²νΌ νλ¨λΆκ° μλ¦Ό. μ±μ΄ μ 체μ μΌλ‘ νλ¨λΆ UIκ° μλ¦Ό
'μλ νμΈμ λͺ¨λ¬΄μ λλ€'λΌλ ν μ€νΈκ° μμΌλ©΄ μ’μ κ² κ°μ. μ΄λ―Έ μ€νλμμμ μκ°νκΈ° λλ¬Έ4f05ab6μμ΄λκ° λ‘κ·ΈμΈμ©μΈμ§ μ¬λλ€μκ² λ³΄μ΄λ μ©μΈμ§ λͺ¨λ₯΄κ² μ(username -> nickname)
νλ‘ν μμ
νλ‘ν μμ κΈ°λ₯μμ μ¬μ§ ν¬κΈ° 2MB μ΄λ΄λΌμ νΈλν°μΌλ‘ μ°μ μ¬μ§μ λ±λ‘μ΄ μ λ¨(src/hooks/useProfileImg.ts)κ³μ μ¬μΈμ¦ λͺ¨λ¬ - λΉλ°λ²νΈκ° λ§μ€νΉ μ²λ¦¬λλ©΄ μ’κ² μ7e75388νλ‘ν μ€μ μ΄ μμ μΈ κ±΄μ§ ν·κ°λ¦Ό6167780νν΄ λͺ¨λ¬ νλ¨λΆκ° μλ¦Ό#227
ν
- νλ¬μ€ μμ΄μ½μ΄ μ¨λ² μΆκ° λ²νΌμΈμ§ ν·κ°λ¦Ό
μ¨λ² μ΄λ¦μ μ λ ₯νμ§ μμλ μ¨λ² μΆκ° κ°λ₯#236μ¨λ² μμ /μμ λͺ¨λ¬μ μλ μ²΄ν¬ νμμ κΈ°λ₯μ λͺ¨λ₯΄κ² μ(μ μ₯ λ²νΌ: μ²΄ν¬ μμ΄μ½ -> 'μ μ₯' ν μ€νΈ)μ¨λ² μμ± μ, μν°λ₯Ό λλ¬μ μ μ₯ κ°λ₯νλ©΄ μ’κ² μ91d0fe3κ°μ μ΄λ¦μΌλ‘ μ¨λ²μ μ¬λ¬ κ° λ§λ€ μ μμ#236νμ¬ μ¨λ²(λ보기 ν΄λ¦ μ, μ ν λͺ¨λ¬μ΄ λ¨λλ‘ λ³κ²½)01e4d5fb825983...
λ²νΌ(λ보기) ν΄λ¦ μ, μ¨λ² μμ /μμ λͺ¨λ¬μ΄ μ΄λ¦¬λλ° κΈ°λ₯μ λͺ λ£ννλ©΄ λ μ’μ κ² κ°μμ¨λ² νλ¨λΆκ° μλ¦Ό571a714μ¨λ²λͺ μ μ λ ₯νκ³ μ μ₯ λ²νΌμ μ¬λ¬ λ² λλ₯΄λ©΄ μ¨λ²μ΄ μ¬λ¬κ° μμ±λ¨692c719μ¨λ² μμ /μμ λͺ¨λ¬μ λ²νΌμ μ¬λ¬ λ² λλ₯΄λ©΄ μλ¬ νμ΄μ§("μ‘΄μ¬νμ§ μλ νμ΄μ§μ λλ€")λ‘ μ΄λλ¨#236
μν© μ¬ν: λ²νΌμ΄ μμΉν κ³³μ κ³μν΄μ ν΄λ¦νλ©΄, μμ±μ΄ μλ£λ λ λͺ¨λ¬μ΄ λ«νλ λμμ μ¨λ²μ΄ ν΄λ¦ λ¨ -> ν΄λ¦ λ μ¨λ²μ μ λͺ©μ΄ μμ μ, μ ν¨νμ§ μμ μ£Όμλ‘ μ΄λλ¨λͺ¨λ¬μ°½μ΄ λμμ‘μ λ λ²νΌμ λλ¬μ§μ§ μμ§λ§ μ€ν¬λ‘€μ΄ κ°λ₯ -> μΌλ°μ μΈ μ¬μ©μ±μ΄λ, κ²μλ¬Ό μ λ‘λ/μμ λͺ¨λ¬μ κ²½μ° λͺ¨λ°μΌμμ νλ©΄ μ 체λ₯Ό μ°¨μ§νκΈ° λλ¬Έμ μ¬μ©μ λΆνΈν¨μ΄ μμ#143μ¨λ² νν° λ²νΌμ΄ λ무 μμ036022aμ¨λ² μ λͺ© κΈμ μμ μ νμ΄ μμ536bae6μ λ ¬ κΈ°μ€ λλ₯΄κ³ λμ λ€λ₯Έ λΆλΆ ν°μΉνμ λ μ λ ¬μ°½μ΄ κΊΌμ‘μΌλ©΄ ν¨9b16fbf
μ¨λ² μμΈ νμ΄μ§
κ²μκΈμ΄ λ§μμ§μλ‘ 'μ¨λ²μ κ²μκΈ μΆκ° λ²νΌ'λ λ€λ‘ λ°λ €μ λλ₯΄λ¬ κ°κΈ° νλ€μ΄μ§5bbe7dd63fa419λͺ©λ‘ν, μ¨λ²ν μ ν κ°λ₯νλ©΄ μ’κ² μfe9614f- λ μ§, μ λͺ©μ΄ 보μμΌλ©΄ μ’κ² μ
κ²μλ¬Ό μμΈ νμ΄μ§
μ λͺ©κ³Ό λ³Έλ¬Έμ΄ κΈμ ν¬κΈ° μ°¨μ΄κ° ν¬κ² μ λμ μμμ΄λ ν°νΈ μ¬μ΄μ¦λ₯Ό μ’ λ μ‘°μ νλ©΄ μ’κ² μ17cc95d
κ²μλ¬Ό μ
λ‘λ
ν λ²μ μ¬λ¬ μ₯μ μ¬μ§μ λ±λ‘ν μ μμΌλ©΄ μ’κ² μ=> λΈλΌμ°μ νμΌ μ ν κΈ°λ₯ μ΅μμ§ μμ μ¬μ©μ- μ²μμ λͺ¨λ selectboxκ° λ«νμμ΄μ ν λ² λ λλ¬μΌ νλ κ² μ¬μ©μ±μ΄ μ μ’μ κ² κ°μ
- λκ° μ§λμ νμ μμ§μ¬μ μμΉλ₯Ό μ νν μ μλ κΈ°λ₯μ΄ μμΌλ©΄ μ’κ² μ
μ²΄ν¬ νμκ° μμΉ μΆκ° λ²νΌμ΄λΌλ κ²μ μκΈ° μ΄λ €μ μ#52μ λ‘λ λ²νΌμ ν¬μ»€μ€κ° λ§μ§λ§μ λλ©΄ μ’κ² μ#74μ€λμ λ μ¨μμ λΉμ λμ ꡬλΆμ΄ λͺ¨νΈν¨89f4104- μ§λμμ λ΄κ° μνλ λΆλΆμ ν°μΉνμ¬ μ§μ ν μ μμΌλ©΄ μ’κ² μ
- μμΉ μΆκ°λμ μ΄μμ λ, μλλ‘ λλκ·Ένμ¬ μ€λμ λ μ¨λ λ³Ό μ μμΌλ©΄ μ’κ² μ
μ¨λ²μ΄ 볡μ μ ν κ°λ₯ν΄μ μ’μESCλ₯Ό λλ₯΄λ©΄ λͺ¨λ¬μ΄ λ«νλ©΄ μ’κ² μ61d1e8aκΈ°μ‘΄ μ¬μ§μ μΆκ°ν μ¬μ§λ§ μ ννλλ°, κΈ°μ‘΄ μ¬μ§μ μμ΄μ§#269μ¬μ§μ΄ μ΅λ 3μ₯μ΄ μ¬λΌκ°λλ° λ°λ‘ μλ΄ μ¬νμ΄ μλ μ μ΄ μμ¬μ#269- μμΉ μΆκ°μ κ²½μ° μ§λμ νμλλ μ₯μλ₯Ό νλνκ³ μΆμνλ κΈ°λ₯μ μ¬μ©ν λ λ§μ½ inputμ 컀μκ° λ€μ΄κ° μλ κ²½μ° (κ²μμ΄λ₯Ό μ λ ₯νκ³ λ°λ‘ μ§λλ₯Ό νλνλ κ²½μ°) νλ μΆμν λλ§λ€ inputμ μκΎΈ ν¬μ»€μ€κ° λ€μ΄κ°. inputμμ ν¬μ»€μ€λ₯Ό λΉΌμΌ(λ€λ‘κ°κΈ° λ²νΌ λλ¬μΌ) κΉλνκ² νλμΆμκ° κ°λ₯ν¨
- κ²μμ΄λ₯Ό λͺ¨νΈνκ² μ λ ₯νμ λ μ νμ§κ° μλκ² μμ¬μ(μμΈλ‘ μ λ ₯νλ©΄ 경볡κΆμ΄ μ νλ¨)
νμ νλͺ©μ μ μ μμ΄μ λΆνΈν¨#269- μ¨λ² μ ν μ, μλ‘μ΄ μ¨λ²μ λ§λ€μ΄μ κ·Έ μ¨λ²μ λ°λ‘ μΆκ°ν μ μμΌλ©΄ μ’κ² μ
μ¬μ§ μ λ‘λ λ²νΌμ ν¬μ»€μ€κ° λλ©΄ μ’κ² μ#74- μ¬μ§λΏλ§ μλλΌ λμμλ μ μ₯ν μ μμΌλ©΄ μ’κ² μ
- νλ°κ° μμ΄λ μ’μ λ―ν¨
μ λ‘λλλ μ¬μ§μ νμ₯λͺ μ μΆκ°νλ©΄ μ’κ² μ(gif λ±)#53κ²μλ¬Ό μ λͺ©κ³Ό κ²μλ¬Ό λ³Έλ¬Έμ 곡백 ν¬ν¨ 500μκΉμ§ μ ν μμ(μλ΄ νμ)#269μμΉ μ ν μ, μ§λκ° λ¨μ§ μλ λ²κ·Έ(Kakao Developersμ μ£Όμ μΆκ°)λ μ¨μ κΈ°λΆ μ λ νΈ λ°μ€κ° μ νλμ§ μλ λ²κ·Έ(κ²μλ¬Ό μ λ‘λ 22 λ²κ·Έλ‘ μΈν λ²κ·Έ)μ λ‘λ λ²νΌμ΄ λλ¬μ§μ§ μμ -> UI νΌλλ°±μ΄ μμ΄μ μ λ‘λμ€μμ μ μ μμ -> λ‘λ©μ€ UI μΆκ°
μ±
μλλ‘μ΄λ κΈ°κΈ°μ λ€λ‘κ°κΈ° λ²νΌμ λλ₯΄λ©΄ μ±μ΄ μ’ λ£λ¨dc43540
κΈ°ν
μ€ν¬λ‘€μ νλ©° κ³μν΄μ κ²μκΈμ λ³΄κ³ μΆμfe9614fμ¨λ²μ νμλ‘ μμ±ν΄μΌ μ¬μ§μ΄ μ λ‘λν μ μκ±°λ, μ¨λ² μμ± μ μ¬μ§μ μ λ‘λν΄λ κΈ°λ³Έ μ¨λ²μ μ¬μ§μ΄ μ λ‘λλλ©΄ μ’κ² μ(
μν©: νμκ°μ μ, 'μ 체 보기' μ¨λ²μ΄ μλ μμ±λλ μ¬μ©μλ μΈμ§νμ§ λͺ»ν¨
ν΄κ²°1: κΈ°μ‘΄μ μ λ‘λ ν μ΄λν νΌλ μμΈ νμ΄μ§μμ ν΄λΉ νΌλμ λν μ λ³΄λ§ λ³Ό μ μμ. 'μ 체 보기'μ νΌλ μμΈ νμ΄μ§λ‘ μ΄λνλλ‘ λ³κ²½νμ¬, 'μ 체 보기' μ¨λ²μ΄ μλ μμ±λκ³ ν΄λΉ μ¨λ²μ μ μ₯λμμμ μΈμ§ν μ μλλ‘ ν¨${userUid}/μ 체 보기/feed
)
ν΄κ²°2: νμκ°μ μ, νν 리μΌμ¬μ§ μ¬λΌμ΄λκ° λμνμ§ μμ3aabb70
3-1. μ λ‘λ
3-2. κ²μλ¬Ό μμΈ νμ΄μ§#35κ²μκΈ μμ ν κ²μκΈμμ#32<
λ€λ‘κ°κΈ° μμ΄μ½μ λλ₯΄λ©΄ λ€μ κ²μκΈ μμ νμ΄μ§κ° λμμ λΆνΈν¨
- κ°μ : κ²μλ¬Ό μμ νμ΄μ§ -> λͺ¨λ¬λ‘ ν΅μΌκ²μκΈ μμ λ₯Ό νλ©΄ κΈ°μ‘΄μ μλ νμ΄μ§κ° μλλΌ μ κ²μκΈ μμ±νλ νμ΄μ§κ° λμμ λΆνΈν¨ - μν©: κ²μλ¬Ό μμ± ν μμ μ μ΄μ νμ΄μ§μΈ μ κ²μκΈ νμ΄μ§λ‘ μ΄λλ¨ - κ°μ : μ κ²μκΈ νμ΄μ§(κ²μλ¬Ό μ λ‘λ νμ΄μ§) -> λͺ¨λ¬λ‘ ν΅μΌ#39confirm μ°½μ΄λ alertμ°½μ μ§μ λ§λ€μ§ μμμ UIκ° μμμ§ μμ#191- μΊλ¦°λ νμμΌλ‘ μ λ‘λν λ μ§λ₯Ό μ§μ ν΄ νμΈν μ μλ κΈ°λ₯μ΄ μμΌλ©΄ μ’κ² μ
- μ± μ κΈ κΈ°λ₯μ΄ μμΌλ©΄ μ’κ² μ
곡μ κΈ°λ₯ μΆκ° ν¬λ§#253- μμ μ΄λ―Έμ§ μ¬μ§μΌλ‘ λ§λ€μ΄μ Έ μ¬μ§μΌλ‘ 곡μ ν μ μμΌλ©΄ μ’κ² μ
λ΄λΉκ²μ΄μ λ°μ ν λ²νΌμ΄ a νκ·Έμ button νκ·Έλ‘ μ΄λ£¨μ΄μ Έμ ν¬μ»€μ€κ° λ λ² λ¨(#241)https://github.com/yonainthefish/MoMoo/issues/241[https://github.com/yonainthefish/MoMoo/commit/270938caf1635d0ee1eb806c8d0c7cd91535da98]λͺ¨λ¬μ΄ λμμ λ, λͺ¨λ¬ λ΄μμλ§ ν¬μ»€μ€κ° μ΄λνλ©΄ μ’κ² μ(#245)[yonaisgood/MOMOO-React#245]μ ν¨νμ§ μμ URL μ μ μ, λ΄λΉκ²μ΄μ λ°λ λμ€λ©΄ μ’μ κ² κ°μ(#231)[yonaisgood/MOMOO-React#231]μμ΄μ½μ ν리ν°κ° λ λ°μ νλ©΄ μ’κ² μ- μ±μ 컨μ
μ΄ λ λΆλͺ
νλ©΄ μ’μ κ² κ°μ
15-1. μ¬μ§ μ λ‘λ λ©μΈμΌ κ²½μ°, μ¨λ² μμΈ νμ΄μ§μμ μ¬μ©μκ° μ λ‘λν μ¬μ§λ€μ΄ λͺ¨μμ λ λΏλ―ν¨μ λλ λ§ν λμμΈ μμ μΆκ°νλ©΄ μ’κ² μ
15-2. μΌκΈ° κΈ°λ₯μ΄ λ©μΈμΌ κ²½μ°,
- ν°νΈ μμ± λ³ν κΈ°λ₯μ μΆκ°νλ©΄ μ’κ² μ
- λμ ν루 κΈ°λΆμ μ£Όκ°/μκ° κ·Έλνλ‘ νμΈν μ μλ κΈ°λ₯ μΆκ°νλ©΄ μ’κ² μ
- ν°νΈ μμ± λ³ν κΈ°λ₯μ μΆκ°νλ©΄ μ’κ² μ
νν 리μΌμ΄λ μ¬μ©λ²μ μλ €μ£Όλ κΈ°λ₯μ΄ μμΌλ©΄ μ’κ² μμ΄μ©κ°μ΄λμ¨λ²μ΄ μ¬λ¬ κ°κ° λλ κ²½μ° μ€ν¬λ‘€μ΄ λ무 κΈΈμ΄μ Έμ νΈλν°μ κ°€λ¬λ¦¬μ²λΌ μμλ μ’μ λ― ν¨- μ μ²΄λ³΄κΈ°κ° μμ μ κ°νν¨
λ§μ΄νμ΄μ§ μ°½μμ μ μ νλ‘ν κΈ°λ₯μ λ°λ‘ μ¬μ©νμ§ μλλ€λ©΄ λΉΌλ©΄ μ’κ² μ(λλ μ€ μκ³ λͺ λ² ν΄λ¦ν¨)#91- λ€λ₯Έ μ¬λκ³Όμ μ±ν κΈ°λ₯μ΄ μμμΌλ©΄ μ μ ν κ² κ°μ
μ¨λ² μμ±, νμνν΄ λͺ¨λ¬ - λ²νΌμ΄ 보μ΄μ§ μμ404 νμ΄μ§μ navκ° μμΌλ©΄ μ’κ² μ(λ€λ₯Έ νμ΄μ§λ‘ μ΄λν μ μλλ‘)
*μ΄ 73κ°μ νΌλλ°±μ λ°μμ΅λλ€. νμ¬ 56κ°λ₯Ό λ°μ μλ£νμ΅λλ€.
μ¬μ© λ§μ‘±λ κ°μ
- 1μ°¨ λ§μ‘±λ μ‘°μ¬ λλΉ νκ· μ½ 29% ν₯μλ¨ (4λ² μ μΈ)
- β 10μ λ§μ
- νμκ°μ /λ‘κ·ΈμΈ/νλ‘ν μμ : β 7.3 -> β 10
- μ¨λ² μμ±/μμ /μμ : β 7.7 -> β 9.5
- κ²μλ¬Ό μ λ‘λ/μμ /μμ : β 6.7 -> β 10
- μ¨λ² 곡μ : 10
νΌλλ°± λͺ©λ‘
ν
- 'λ보기'λ₯Ό λλ₯΄λ©΄ (μμ νκΈ°, 곡μ λμ) μ΄μΈμ μμ νκΈ°λ μμμΌλ©΄ ν¨
- μ¨λ²μμ μ¬μ§μ λλ₯΄κ±°λ νλλ₯Ό ν΅ν΄ μ¬μ§μ μ’ λ μμΈν λ³Ό μ μμΌλ©΄ μ’μ κ² κ°μ
μ¨λ² ν΄λ¦ μ, 404 νλ©΄μ΄ λ¨λ κ²½μ°κ° μμ -> μ¨λ² μ΄λ¦μ΄ '.'μΈ κ²½μ°#145
κ²μλ¬Ό μμΈ
μ€μ μ λ‘λν λ μ§μ λ€μλ λ‘ λμ΄412e163
κ²μλ¬Ό μ
λ‘λ/μμ /μμ
- κΈ°μ‘΄μ μ¨λ²μ λ€μ΄κ° μλ μνμμ μ λ‘λλ₯Ό λλ¬λ μ¨λ² μ νμ λ°λ‘ λλ¬μΌ νλλ°, λ§μ½ μ¨λ²μ λ€μ΄κ° μνμμ μ λ‘λνκ³ μ νλ©΄ μ μ λ‘ μ΄λ€ μ¨λ²μΈμ§ μ νλλ©΄ μ’κ² μ
- μκ°μ΄ μ€λ 걸리λ κ²½μ° μμ
- μ¬μ§μ μ¬λ¬μ₯ μ¬λ¦΄ λ λ€μ€ μ νμ΄ κ°λ₯νλ©΄ μ’κ² μ
곡μ μ¨λ²
- 곡μ μ¨λ²μ λ§λ€κ³ μ¬μ©μλ₯Ό μ΄λνλ©΄ μ¬μ§μ κ°μ΄ λ³Ό μ μλ€λ κ² μ¬λ°λ λΆλΆμΈ κ±° κ°μ
- 곡μ μ¨λ²μ λ§λ€ λ, μ¬μ©μλ₯Ό μ΄λν΄μΌλ§ 곡μ μ¨λ²(ν)μ μ¨λ²μ΄ μκΈ°λ κ²μ λͺ°λμ. μ€λͺ μ΄ νμν κ² κ°μ
κΈ°ν
μ λ‘λ λͺ¨λ¬μμ κΈ°κΈ°μ λ€λ‘κ°κΈ°λ₯Ό λλ₯΄λ©΄ μ΄νμ΄ μ’ λ£λλ νμμ΄ λνλ¨ -> AndroidManifest.xmlμμ λλ μ§μνμ§ μλ μμ±μ μ κ±°ν ν ν΄κ²°λ¨832a5ab- μ±μ€ν μ΄λ‘λ μ€μΉνκ³ μΆμ
- 곡μ μ¨λ²μ λ°μμ λ¨κΈΈ μ μμΌλ©΄ μ’κ² μ. μ’μμ νΉμ μ€ν°μ»€. λ€μν μ€ν°μ»€λ₯Ό ν맀νμΌλ©΄ μ’κ² μ
- 곡μ λ°μ μ¨λ²μ μ¬μ§μ μ¬λ¦¬κ³ μΆμ
- μ¬μ§ 보μ λ ν μ μμΌλ©΄ μ’κ² μ. λ°κΈ° μ‘°μ , μ±λ μ‘°μ λ±
- μ λ‘λ λ μ§λ νμ¬ μλμΌλ‘ μ μ₯λλλ°, μ§μ μμ κ°λ₯νλ©΄ μ’κ² μ
-
Admin SDK μ¬μ©: v2μ μ¨λ² 곡μ κΈ°λ₯ μΆκ°λ₯Ό μν΄ Admin SDKλ₯Ό μ¬μ©ν΄μΌ νκ³ , μ΄λ₯Ό μν΄ μλ² νκ²½μ ꡬμΆν΄μΌ νμ΅λλ€.
-
μλ² μ¬μ΄λ λ λλ§ (SSR) μ§μ: SSRμ ν΅ν΄ SEOλ₯Ό ν₯μμν€κ³ μ΄κΈ° νμ΄μ§ λ‘λ© μλλ₯Ό κ°μ ν μ μμ΅λλ€.
μ΄κΈ° λ λλ§ μλ μ½ 78% κ°μ (FCP, LCO, Speed Index)
μ¨λ² 곡μ (μ κΈ°λ₯)
κ²μλ¬Ό μ λ‘λ λͺ¨λ¬
- κΈ°μ‘΄: μ¬μ§ μ ν λ° μ 체 μ¬μ ν κ°λ₯
- κ°μ : μ¬μ§ μ ν ν, μΌλΆ μμ λ° μΆκ° μ ν κ°λ₯
![]() |
π | ![]() |
v1 | v2 |
API μλν¬μΈνΈ
-
κΈ°μ‘΄: νμ΄μ΄λ² μ΄μ€μμ λͺ¨λ ν΅μ μ ν΄λΌμ΄μΈνΈμμ μ²λ¦¬νμ΅λλ€.
-
κ°μ : νμ΄μ΄λ² μ΄μ€μμ ν΅μ λ‘μ§μ λ°±μλλ‘ λΆλ¦¬ν μ΄μ λ λ€μκ³Ό κ°μ΅λλ€.
- μ μ§λ³΄μμ± ν₯μμ μν λΉμ¦λμ€ λ‘μ§ λΆλ¦¬
- μμ² μ€λ¨μΌλ‘ μΈν λ°μ΄ν° μμ€ λ°©μ§
- 보μ κ°ν
// src/app/api/user/route/ts export async function DELETE() { const uid = cookies().get('uid')?.value; if (!uid) { return NextResponse.json( { error: 'μΈμ¦λμ§ μμ μ¬μ©μμ λλ€.' }, { status: 401 } ); } try { const { photoURL } = await getUserByUid(uid); await Promise.all([ deletePhothURL(photoURL), deleteFeedsImg(uid), deleteUserDoc(uid), deleteAlbumDocs(uid), deleteFeedDocs(uid) ]); await adminAppAuth.deleteUser(uid); } catch (error) { console.error(error); return NextResponse.json( { error: 'κ³μ μμ μ€ μκΈ°μΉ λͺ»ν μ€λ₯κ° λ°μνμ΅λλ€.' }, { status: 500 } ); } return NextResponse.json({ status: 204 }); }
6. μ μ νΌλλ°±μ μ°Έκ³ ν΄μ£ΌμΈμ :)
Firestore Database
// {uid}/{uid}
{
sharedAlbums: Reference(albumDoc){}
}
// {uid}/{uid}/album/{albumId}
{
createdTime: Timestamp;
feedList: String(feedId)[];
name: String;
sharedUsers: {uid:String; permission: "read"}[];
}
// {uid}/{uid}/feed/{feedId}
{
id: String;
title: String;
text: String;
seletedAddress: String;
emotionImage: String;
weatherImage: String;
timestamp: Timestamp;
}
Storage
feed/{feedId + imageIndex}.{νμ₯μ}
profile/{uid}.{νμ₯μ}