์ง๋ ๋ทฐํฌํธ ๊ธฐ๋ฐ ์ค์๊ฐ ์์ ๊ฒ์ ๊ตฌํ
๐ ๊ฐ์
์ฌ์ฉ์๊ฐ ์ง๋๋ฅผ ์ด๋ํ๊ฑฐ๋ ํ๋/์ถ์ํ ๋, ํ์ฌ ํ๋ฉด์ ๋ณด์ด๋ ์์ญ(๋ทฐํฌํธ) ๋ด์ ์์๋ง ์ค์๊ฐ์ผ๋ก ๊ฒ์ํ์ฌ ํ์ํ๋ ๊ธฐ๋ฅ์ ๋๋ค.
Airbnb์ ๊ฐ์ ์ง๋ ๊ธฐ๋ฐ ๊ฒ์ ์๋น์ค์์ ๋๋ฆฌ ์ฌ์ฉ๋๋ ํจํด์ผ๋ก, ์ฌ์ฉ์๊ฐ ๊ด์ฌ ์๋ ์ง์ญ์ ์์๋ฅผ ํจ์จ์ ์ผ๋ก ํ์ํ ์ ์์ต๋๋ค.

๐ฏ ํต์ฌ ๊ฐ๋
1. ๋ทฐํฌํธ(Viewport)๋?
๋ทฐํฌํธ๋ ์ง๋์์ ํ์ฌ ํ๋ฉด์ ๋ณด์ด๋ ์ฌ๊ฐํ ์์ญ์ ์๋ฏธํฉ๋๋ค.
๋ถ(North)
↑
|
์(West) ←--+--→ ๋(East)
|
↓
๋จ(South)
โโโโโโโโโโโโโโโโโโโ ← NE (๋ถ๋์ชฝ) ๋ชจ์๋ฆฌ
โ โ ์ขํ: (neLat, neLng)
โ ์ง๋ ํ๋ฉด โ
โ (๋ทฐํฌํธ) โ
โ โ
โโโโโโโโโโโโโโโโโโโ ← SW (๋จ์์ชฝ) ๋ชจ์๋ฆฌ
↑ ์ขํ: (swLat, swLng)
2. Bounds (๊ฒฝ๊ณ) ์ขํ
๋ทฐํฌํธ๋ ๋ ๊ฐ์ ๋ชจ์๋ฆฌ ์ขํ๋ก ์๋ฒฝํ๊ฒ ์ ์๋ฉ๋๋ค:
- SW (SouthWest): ๋จ์์ชฝ ๋ชจ์๋ฆฌ - ์ผ์ชฝ ์๋ ๋
swLat: ๋จ์์ชฝ ์๋ (๊ฐ์ฅ ๋ฎ์ ์๋)swLng: ๋จ์์ชฝ ๊ฒฝ๋ (๊ฐ์ฅ ๋ฎ์ ๊ฒฝ๋)
- NE (NorthEast): ๋ถ๋์ชฝ ๋ชจ์๋ฆฌ - ์ค๋ฅธ์ชฝ ์ ๋
neLat: ๋ถ๋์ชฝ ์๋ (๊ฐ์ฅ ๋์ ์๋)neLng: ๋ถ๋์ชฝ ๊ฒฝ๋ (๊ฐ์ฅ ๋์ ๊ฒฝ๋)
์์: ์์ธ ์๋ด ๋ทฐํฌํธ
{
swLat: 37.50, // ๋จ์ชฝ ๊ฒฝ๊ณ
swLng: 126.95, // ์์ชฝ ๊ฒฝ๊ณ
neLat: 37.60, // ๋ถ์ชฝ ๊ฒฝ๊ณ
neLng: 127.05 // ๋์ชฝ ๊ฒฝ๊ณ
}
๐๏ธ ์ํคํ ์ฒ
์ ์ฒด ํ๋ฆ๋
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ์ฌ์ฉ์ ์ก์
โ
โ (์ง๋ ์ด๋, ํ๋/์ถ์, ๋๋๊ทธ) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MapView ์ปดํฌ๋ํธ โ
โ - ์นด์นด์ค๋งต API์์ bounds ์ ๋ณด ํ๋ โ
โ - bounds_changed ์ด๋ฒคํธ ๋ฆฌ์ค๋ โ
โ - ๋๋ฐ์ด์ฑ ์์ด ์ฆ์ ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ก ์ ๋ฌ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ onMapBoundsChange({ swLat, swLng, neLat, neLng })
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SearchResultPage ์ปดํฌ๋ํธ โ
โ - handleMapBoundsChange์์ bounds ์์ โ
โ - 500ms ๋๋ฐ์ด์ฑ ์ ์ฉ (๊ณผ๋ํ API ํธ์ถ ๋ฐฉ์ง) โ
โ - SearchParams ๊ตฌ์ฑ (๊ธฐ์กด ํํฐ + bounds) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ searchAccommodations(params)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Backend API Server โ
โ GET /accommodations?swLat=...&swLng=...&neLat=...&neLng=...โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AccommodationService (NestJS) โ
โ - bounds ํ๋ผ๋ฏธํฐ ์ฐ์ ๊ฒ์ฌ โ
โ - SQL WHERE ์ ๋ก ์ฌ๊ฐํ ์์ญ ํํฐ๋ง โ
โ - ๋ทฐํฌํธ ๋ด ์์๋ง ๋ฐํ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ { items: [...], total, page, ... }
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ํ๋ฉด์ ๊ฒฐ๊ณผ ํ์ โ
โ - ์ข์ธก: ์์ ๋ฆฌ์คํธ ์
๋ฐ์ดํธ โ
โ - ์ฐ์ธก: ์ง๋ ๋ง์ปค ์
๋ฐ์ดํธ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ป ๊ตฌํ ์์ธ
1. ํ๋ก ํธ์๋: MapView ์ปดํฌ๋ํธ
์ญํ
- ์นด์นด์ค๋งต API์์ ๋ทฐํฌํธ bounds ์ ๋ณด ์ถ์ถ
- ์ง๋ ์ด๋/์ค ๋ณ๊ฒฝ ์ ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ก bounds ์ ๋ฌ
ํต์ฌ ์ฝ๋
// frontend/src/components/map/MapView.tsx
// bounds_changed ์ด๋ฒคํธ ๋ฆฌ์ค๋
useEffect(() => {
if (!map) return;
const updateBounds = () => {
// ์นด์นด์ค๋งต์์ ํ์ฌ ๋ทฐํฌํธ์ ๊ฒฝ๊ณ ์ขํ ํ๋
const mapBounds = map.getBounds();
const sw = mapBounds.getSouthWest(); // ๋จ์์ชฝ ๋ชจ์๋ฆฌ
const ne = mapBounds.getNorthEast(); // ๋ถ๋์ชฝ ๋ชจ์๋ฆฌ
const newBounds = {
swLat: sw.getLat(), // ๋จ์์ชฝ ์๋
swLng: sw.getLng(), // ๋จ์์ชฝ ๊ฒฝ๋
neLat: ne.getLat(), // ๋ถ๋์ชฝ ์๋
neLng: ne.getLng(), // ๋ถ๋์ชฝ ๊ฒฝ๋
};
// ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ก bounds ์ ๋ฌ
onMapBoundsChange?.(newBounds);
};
// bounds_changed ์ด๋ฒคํธ ๋ฑ๋ก
// (zoom_changed, dragend ๋ฐ์ ์์๋ ์๋์ผ๋ก ํธ๋ฆฌ๊ฑฐ๋จ)
kakao.maps.event.addListener(map, 'bounds_changed', updateBounds);
return () => {
kakao.maps.event.removeListener(map, 'bounds_changed', updateBounds);
};
}, [map, onMapBoundsChange]);
์ฃผ์ ํฌ์ธํธ
getBounds()๋ฉ์๋: ์นด์นด์ค๋งต API๊ฐ ์ ๊ณตํ๋ ํ์ฌ ๋ทฐํฌํธ ์ ๋ณดbounds_changed์ด๋ฒคํธ: ์ง๋ ์ด๋/์ค ๋ณ๊ฒฝ ์ ์๋ ๋ฐ์- ์ฆ์ ์ ๋ฌ: MapView์์๋ ๋๋ฐ์ด์ฑ ์์ด ์ฆ์ ๋ถ๋ชจ๋ก ์ ๋ฌ
2. ํ๋ก ํธ์๋: SearchResultPage ์ปดํฌ๋ํธ
์ญํ
- MapView๋ก๋ถํฐ bounds ์์
- ๋๋ฐ์ด์ฑ ์ ์ฉํ์ฌ ๊ณผ๋ํ API ํธ์ถ ๋ฐฉ์ง
- ๊ธฐ์กด ๊ฒ์ ํํฐ + bounds๋ฅผ ๊ฒฐํฉํ์ฌ API ํธ์ถ
ํต์ฌ ์ฝ๋
// frontend/src/pages/SearchResultPage.tsx
const handleMapBoundsChange = useCallback(
(bounds: {
swLat: number;
swLng: number;
neLat: number;
neLng: number;
}) => {
// ๊ธฐ์กด ํ์ด๋จธ ์ทจ์
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
// 500ms ํ์ API ํธ์ถ (๋๋ฐ์ด์ฑ)
debounceTimerRef.current = window.setTimeout(async () => {
setIsLoading(true);
setError(null);
try {
// ๊ธฐ์กด ๊ฒ์ ํ๋ผ๋ฏธํฐ ์ ์ง + bounds ์ถ๊ฐ
const params = parseSearchParams();
params.swLat = bounds.swLat;
params.swLng = bounds.swLng;
params.neLat = bounds.neLat;
params.neLng = bounds.neLng;
const response = await searchAccommodations(params);
const convertedData = response.items.map(convertToAccommodation);
setAccommodations(convertedData);
} catch (err) {
console.error('์ง๋ ์ด๋ ์ค ์์ ๊ฒ์ ์ค๋ฅ:', err);
} finally {
setIsLoading(false);
}
}, 500);
},
[parseSearchParams]
);
์ฃผ์ ํฌ์ธํธ
- ๋๋ฐ์ด์ฑ (500ms): ์ฌ์ฉ์๊ฐ ์ง๋๋ฅผ ๊ณ์ ๋๋๊ทธํ๋ ๋์ API ํธ์ถ ๋ฐฉ์ง
- ๊ธฐ์กด ํํฐ ์ ์ง: ๋ ์ง, ๊ฐ๊ฒฉ, ์ธ์ ๋ฑ ๊ธฐ์กด ๊ฒ์ ์กฐ๊ฑด ์ ์ง
- ์๋ฌ ์ฒ๋ฆฌ: ์๋ฌ ๋ฐ์ ์ ๊ธฐ์กด ๋ชฉ๋ก ์ ์ง (UX ํฅ์)
3. ๋ฐฑ์๋: DTO (Data Transfer Object)
์ญํ
- API ์์ฒญ ํ๋ผ๋ฏธํฐ ์ ์ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฆ
ํต์ฌ ์ฝ๋
// backend/src/modules/accommodation/dto/search-accommodation.dto.ts
export class SearchAccommodationDto {
// ์ง๋ ๋ทฐํฌํธ ๊ธฐ๋ฐ ๊ฒ์ (์ฌ๊ฐํ ์์ญ)
@IsOptional()
@Type(() => Number)
@IsNumber()
swLat?: number; // ๋จ์์ชฝ ์๋
@IsOptional()
@Type(() => Number)
@IsNumber()
swLng?: number; // ๋จ์์ชฝ ๊ฒฝ๋
@IsOptional()
@Type(() => Number)
@IsNumber()
neLat?: number; // ๋ถ๋์ชฝ ์๋
@IsOptional()
@Type(() => Number)
@IsNumber()
neLng?: number; // ๋ถ๋์ชฝ ๊ฒฝ๋
// ... ๊ธฐํ ํํฐ (๋ ์ง, ๊ฐ๊ฒฉ, ์ธ์ ๋ฑ)
}
4. ๋ฐฑ์๋: Service ๋ ์ด์ด
์ญํ
- bounds ๊ธฐ๋ฐ SQL ์ฟผ๋ฆฌ ์์ฑ
- ๋ทฐํฌํธ ๋ด ์์๋ง ํํฐ๋ง
ํต์ฌ ์ฝ๋
// backend/src/modules/accommodation/accommodation.service.ts
async findAll(searchDto: SearchAccommodationDto) {
const { swLat, swLng, neLat, neLng } = searchDto;
const query = this.accommodationRepository
.createQueryBuilder('accommodation')
.leftJoinAndSelect('accommodation.amenities', 'amenity')
.leftJoinAndSelect('accommodation.host', 'host')
.leftJoinAndSelect('accommodation.reviews', 'review');
// ์ง๋ ๊ธฐ๋ฐ ๊ฒ์
// 1. bounds๊ฐ ์์ผ๋ฉด ๋ทฐํฌํธ ๊ธฐ๋ฐ ์ฌ๊ฐํ ๊ฒ์
// 2. bounds๊ฐ ์์ผ๋ฉด ์ค์ฌ์ + ๋ฐ๊ฒฝ ๊ธฐ๋ฐ ๊ฒ์ (ํ์ ํธํ์ฑ)
const hasBounds =
swLat !== undefined &&
swLng !== undefined &&
neLat !== undefined &&
neLng !== undefined;
if (hasBounds) {
// ๋ทฐํฌํธ ๊ธฐ๋ฐ ์ฌ๊ฐํ ๊ฒ์
query
.andWhere('accommodation.latitude BETWEEN :swLat AND :neLat', {
swLat,
neLat,
})
.andWhere('accommodation.longitude BETWEEN :swLng AND :neLng', {
swLng,
neLng,
});
} else {
// ์ค์ฌ์ + ๋ฐ๊ฒฝ ๊ธฐ๋ฐ ๊ฒ์ (๊ธฐ๋ณธ๊ฐ: ์์ธ ์ค์ฌ 10km)
// ... ๊ธฐ์กด ๋ก์ง
}
// ... ๊ธฐํ ํํฐ (๊ฐ๊ฒฉ, ์ธ์, ์์ฝ ๋ ์ง ๋ฑ)
const [items, total] = await query.getManyAndCount();
return { items, total, page, limit, totalPages };
}
์์ฑ๋๋ SQL ์ฟผ๋ฆฌ ์์
SELECT accommodation.*, amenity.*, host.*, review.*
FROM accommodations accommodation
LEFT JOIN accommodation_amenities ON ...
LEFT JOIN amenities amenity ON ...
LEFT JOIN users host ON ...
LEFT JOIN reviews review ON ...
WHERE accommodation.latitude BETWEEN 37.50 AND 37.60
AND accommodation.longitude BETWEEN 126.95 AND 127.05
-- ๊ธฐํ ํํฐ ์กฐ๊ฑด๋ค
ORDER BY ...
LIMIT 20 OFFSET 0;
๐ ๋ฐ์ดํฐ ํ๋ฆ ์์
์๋๋ฆฌ์ค: ์ฌ์ฉ์๊ฐ ์ง๋๋ฅผ ๊ฐ๋จ์ญ์์ ํ๋๋ก ๋๋๊ทธ
- ์ฌ์ฉ์ ์ก์
์ง๋๋ฅผ ๊ฐ๋จ์ญ → ํ๋๋ก ๋๋๊ทธ- MapView: bounds ์ถ์ถ
{ swLat: 37.54, // ํ๋ ๋จ์ชฝ swLng: 126.91, // ํ๋ ์์ชฝ neLat: 37.56, // ํ๋ ๋ถ์ชฝ neLng: 126.93 // ํ๋ ๋์ชฝ }- SearchResultPage: ๋๋ฐ์ด์ฑ
์ฌ์ฉ์๊ฐ ๋๋๊ทธ๋ฅผ ๋ฉ์ถ ํ 500ms ๋๊ธฐ → API ํธ์ถ ์ค๋น- API ์์ฒญ
GET /accommodations?swLat=37.54&swLng=126.91&neLat=37.56&neLng=126.93 &checkIn=2024-01-01&checkOut=2024-01-02 &minPrice=50000&maxPrice=200000- ๋ฐฑ์๋: SQL ์คํ
WHERE latitude BETWEEN 37.54 AND 37.56 AND longitude BETWEEN 126.91 AND 126.93 AND price_per_night BETWEEN 50000 AND 200000 -- ์์ฝ ๊ฐ๋ฅ ์ฌ๋ถ ์ฒดํฌ ๋ฑ- ์๋ต
{ "items": [ { "id": 123, "title": "ํ๋ ๊ทผ์ฒ ์๋ํ ์์", "latitude": 37.55, "longitude": 126.92, "price_per_night": 80000, ... }, // ... ํ๋ ์ง์ญ ์์๋ค๋ง ], "total": 45, "page": 1, "limit": 20 }- ํ๋ฉด ์ ๋ฐ์ดํธ
- ์ข์ธก ๋ฆฌ์คํธ: ํ๋ ์ง์ญ ์์ 45๊ฐ ํ์ - ์ฐ์ธก ์ง๋: ํด๋น ์์๋ค์ ๋ง์ปค๋ง ํ์
โก ์ฑ๋ฅ ์ต์ ํ
1. ๋๋ฐ์ด์ฑ (Debouncing)
๋ฌธ์ : ์ฌ์ฉ์๊ฐ ์ง๋๋ฅผ ๋น ๋ฅด๊ฒ ๋๋๊ทธํ๋ฉด ์ด๋น ์์ญ ๋ฒ์ API ํธ์ถ ๋ฐ์
ํด๊ฒฐ์ฑ : 500ms ๋๋ฐ์ด์ฑ ์ ์ฉ
// ๋ง์ง๋ง ์ด๋ฒคํธ ๋ฐ์ ํ 500ms ๋์ ์ถ๊ฐ ์ด๋ฒคํธ๊ฐ ์์ผ๋ฉด API ํธ์ถ
debounceTimerRef.current = window.setTimeout(async () => {
// API ํธ์ถ
}, 500);
ํจ๊ณผ:
- API ํธ์ถ ํ์: ์ด๋น 30ํ → 2์ด์ 1ํ
- ์๋ฒ ๋ถํ ๋ํญ ๊ฐ์
- ๋คํธ์ํฌ ํธ๋ํฝ ์ ๊ฐ
2. SQL ์ธ๋ฑ์ค
ํ์ ์ธ๋ฑ์ค:
-- ์๋/๊ฒฝ๋์ ๊ณต๊ฐ ์ธ๋ฑ์ค ์์ฑ
CREATE INDEX idx_accommodation_location
ON accommodations(latitude, longitude);
-- ๋๋ ๋ณตํฉ ์ธ๋ฑ์ค
CREATE INDEX idx_accommodation_lat ON accommodations(latitude);
CREATE INDEX idx_accommodation_lng ON accommodations(longitude);
ํจ๊ณผ:
- ์ฟผ๋ฆฌ ์คํ ์๊ฐ: 1000ms → 10ms (์์)
- BETWEEN ์กฐ๊ฑด ๊ฒ์ ์ต์ ํ
3. ํ์ด์ง๋ค์ด์
๊ธฐ๋ณธ ์ค์ : ํ ๋ฒ์ 20๊ฐ์ฉ๋ง ์กฐํ
limit?: number = 20;
ํจ๊ณผ:
- ์๋ต ํฌ๊ธฐ ๊ฐ์
- ์ด๊ธฐ ๋ก๋ฉ ์๋ ํฅ์
๐จ ์ฌ์ฉ์ ๊ฒฝํ (UX)
1. ๋ก๋ฉ ์ํ ํ์
{isLoading && (
<div className="flex flex-col gap-4">
{Array.from({ length: 5 }).map((_, index) => (
<AccommodationCardSkeleton key={index} />
))}
</div>
)}
ํจ๊ณผ:
- ์ฌ์ฉ์๋ ๋ฐ์ดํฐ๊ฐ ๋ก๋ฉ ์ค์์ ๋ช ํํ ์ธ์ง
- ์ค์ผ๋ ํค UI๋ก ๋ ์ด์์ ๋ณํ ์ต์ํ
2. ์๋ฌ ์ฒ๋ฆฌ
catch (err) {
console.error('์ง๋ ์ด๋ ์ค ์์ ๊ฒ์ ์ค๋ฅ:', err);
// ์๋ฌ ๋ฐ์ ์ ๊ธฐ์กด ๋ชฉ๋ก ์ ์ง (์ฌ์ฉ์ ๊ฒฝํ ํฅ์)
}
ํจ๊ณผ:
- ๋คํธ์ํฌ ์ค๋ฅ ์์๋ ๊ธฐ์กด ๋ฐ์ดํฐ ์ ์ง
- ์ฌ์ฉ์ ๊ฒฝํ ์ค๋จ ๋ฐฉ์ง
๐ ๋์: ์ํ ๋ฐ๊ฒฝ ๊ฒ์ vs ์ฌ๊ฐํ ๋ทฐํฌํธ ๊ฒ์
์ํ ๋ฐ๊ฒฝ ๊ฒ์ (Haversine)
// ์ค์ฌ์ + ๋ฐ๊ฒฝ์ผ๋ก ๊ฒ์
{
lat: 37.5665,
lng: 126.978,
radius: 5 // 5km ๋ฐ๊ฒฝ
}
์ฅ์ :
- ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ ์ ํํ ๊ฒ์
- ์ค์ฌ์ ์์ ์ผ์ ๊ฑฐ๋ฆฌ ์ด๋ด ๋ณด์ฅ
๋จ์ :
- ํ๋ฉด์ ๋ณด์ด์ง ์๋ ์์๋ ํฌํจ๋ ์ ์์
- ์ง๋ ๋ชจ์๊ณผ ๋ถ์ผ์น (์ง๋๋ ์ฌ๊ฐํ, ๊ฒ์์ ์ํ)
์ฌ๊ฐํ ๋ทฐํฌํธ ๊ฒ์ (ํ์ฌ ๊ตฌํ) โ
// ํ์ฌ ํ๋ฉด ์์ญ์ผ๋ก ๊ฒ์
{
swLat: 37.50,
swLng: 126.95,
neLat: 37.60,
neLng: 127.05
}
์ฅ์ :
- ์ ํ์ฑ: ํ๋ฉด์ ๋ณด์ด๋ ์์ญ๊ณผ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์ ํํ ์ผ์น
- ์ฑ๋ฅ: SQL BETWEEN ์ฟผ๋ฆฌ๋ก ๋น ๋ฅธ ๊ฒ์
- ์ง๊ด์ฑ: ์ฌ์ฉ์๊ฐ ๋ณด๋ ๊ฒ = ๊ฒ์ ๊ฒฐ๊ณผ
๋จ์ :
- ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ ์ ๋ ฌ์ ๋ณ๋ ๊ณ์ฐ ํ์
๐ ํ ์คํธ ์๋๋ฆฌ์ค
1. ๊ธฐ๋ณธ ๋์ ํ ์คํธ
1. ์ง๋๋ฅผ ์์ธ ์์ฒญ์ผ๋ก ์ด๋
2. 500ms ํ API ํธ์ถ ํ์ธ
3. ์์ธ ์์ฒญ ์ฃผ๋ณ ์์๋ง ํ์๋๋์ง ํ์ธ
2. ๋๋ฐ์ด์ฑ ํ ์คํธ
1. ์ง๋๋ฅผ ๋น ๋ฅด๊ฒ ์ฌ๋ฌ ๋ฒ ๋๋๊ทธ
2. ๋ง์ง๋ง ๋๋๊ทธ ์ข
๋ฃ ํ 500ms์๋ง API ํธ์ถ๋๋์ง ํ์ธ
3. ์ค๊ฐ ๋๋๊ทธ๋ค์ API ํธ์ถ ์ ๋๋์ง ํ์ธ
3. ํํฐ ์ ์ง ํ ์คํธ
1. ๋ ์ง, ๊ฐ๊ฒฉ, ์ธ์ ํํฐ ์ค์
2. ์ง๋ ์ด๋
3. ์ค์ ํ ํํฐ๊ฐ ์ ์ง๋๋ฉด์ ์ง์ญ๋ง ๋ณ๊ฒฝ๋๋์ง ํ์ธ
4. ์๋ฌ ์ฒ๋ฆฌ ํ ์คํธ
1. ๋คํธ์ํฌ ์ฐ๊ฒฐ ๋๊ธฐ
2. ์ง๋ ์ด๋
3. ๊ธฐ์กด ์์ ๋ชฉ๋ก์ด ์ ์ง๋๋์ง ํ์ธ
๐ ํฅํ ๊ฐ์ ๋ฐฉ์
1. ๋ฌดํ ์คํฌ๋กค
// ํ์ฌ: ํ์ด์ง๋ค์ด์
// ๊ฐ์ : ์คํฌ๋กค ์ ์๋์ผ๋ก ๋ค์ ํ์ด์ง ๋ก๋
2. ํด๋ฌ์คํฐ๋ง
// ์ค ์์ ์ ๊ฐ๋ณ ๋ง์ปค ๋์ ํด๋ฌ์คํฐ ํ์
// ์: "๊ฐ๋จ๊ตฌ (25๊ฐ)"
3. ์บ์ฑ
// ์ด๋ฏธ ์กฐํํ ์์ญ์ ๋ฐ์ดํฐ๋ฅผ ์บ์
// ๊ฐ์ ์์ญ์ผ๋ก ๋์๊ฐ ๋ API ์ฌํธ์ถ ๋ฐฉ์ง
4. WebSocket ์ค์๊ฐ ์ ๋ฐ์ดํธ
// ์๋ก์ด ์์ ๋ฑ๋ก ์ ์ค์๊ฐ ๋ง์ปค ์ถ๊ฐ
// ์์ฝ ์๋ฃ ์ ์ค์๊ฐ ๋ง์ปค ์ ๊ฑฐ
๐ ์ฐธ๊ณ ์๋ฃ
- Kakao Maps JavaScript API ๋ฌธ์
- TypeORM QueryBuilder ๋ฌธ์
- React ๋๋ฐ์ด์ฑ ํจํด
- Airbnb Engineering Blog
๐ ๊ฒฐ๋ก
์ง๋ ๋ทฐํฌํธ ๊ธฐ๋ฐ ์ค์๊ฐ ๊ฒ์์ ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๊ฐ์ง๋๋ค.
โ
์ ํ์ฑ: ํ๋ฉด์ ๋ณด์ด๋ ์์ญ = ๊ฒ์ ๊ฒฐ๊ณผ
โ
์ฑ๋ฅ: SQL ์ธ๋ฑ์ค + ๋๋ฐ์ด์ฑ์ผ๋ก ์ต์ ํ
โ
์ฌ์ฉ์ ๊ฒฝํ: ์ค์ผ๋ ํค UI + ์๋ฌ ์ฒ๋ฆฌ๋ก ์์ ์ ์ธ UX
โ
ํ์ฅ์ฑ: ์ถ๊ฐ ํํฐ์ ์ฝ๊ฒ ๊ฒฐํฉ ๊ฐ๋ฅ
์ด ๊ตฌ์กฐ๋ ์ง๋ ๊ธฐ๋ฐ ๊ฒ์ ์๋น์ค์ ํ์ค ํจํด์ด๋ฉฐ, ๋๊ท๋ชจ ํธ๋ํฝ์์๋ ์์ ์ ์ผ๋ก ๋์ํฉ๋๋ค.
'๐ ํ๋ก ํธ์๋(FE)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| CSR / SSR / SSG / ISR ๋ ๋๋ง ๋ฐฉ์ ๋น๊ต (1) | 2026.01.17 |
|---|---|
| Next.js 16 + Turbopack + pnpm ๋ชจ๋ ธ๋ ํฌ: Docker ๋น๋ ์์ ์ ๋ณต (0) | 2026.01.17 |
| ์น ์ ๋๋ฉ์ด์ ์ ๋๋ก ํ์ฉํ๊ธฐ: Transition, Keyframes, rAF, WAAPI ๋น๊ต์ ํ์ฉ๋ฒ (0) | 2025.09.24 |
| Webpack Tree Shaking ๋์ ์๋ฆฌ (0) | 2025.09.17 |
| Webpack + Babel + TypeScript ์ค์ ๊ฐ์ด๋ (0) | 2025.09.16 |