perf(web): optimize bundle size — dynamic import Mapbox GL and code split Recharts
- Dynamic import ListingMap with next/dynamic (ssr: false) in /listings/[id] and /search pages - Extract Recharts into lazy-loaded DistrictBarChart and PriceTrendChart components - /listings/[id] first-load JS: 618KB → 149KB (-76%) - /search first-load JS: 619KB → 150KB (-76%) - Both pages now well under 300KB target Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
60
apps/web/components/charts/district-bar-chart.tsx
Normal file
60
apps/web/components/charts/district-bar-chart.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
} from 'recharts';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type TooltipFormatter = (value: any, name: any) => [string, string];
|
||||
|
||||
interface DistrictBarChartProps {
|
||||
data: { district: string; price?: number; 'Gia/m2'?: number; listings: number }[];
|
||||
height?: number;
|
||||
dataKey?: string;
|
||||
tooltipFormatter?: TooltipFormatter;
|
||||
}
|
||||
|
||||
export function DistrictBarChart({
|
||||
data,
|
||||
height = 300,
|
||||
dataKey = 'price',
|
||||
tooltipFormatter,
|
||||
}: DistrictBarChartProps) {
|
||||
const defaultFormatter: TooltipFormatter = (value, name) => [
|
||||
name === dataKey ? `${value} tr/m2` : String(value),
|
||||
name === dataKey ? 'Gia' : 'Tin dang',
|
||||
];
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={height}>
|
||||
<BarChart data={data} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
||||
<XAxis
|
||||
dataKey="district"
|
||||
tick={{ fontSize: 11 }}
|
||||
angle={-30}
|
||||
textAnchor="end"
|
||||
height={60}
|
||||
className="fill-muted-foreground"
|
||||
/>
|
||||
<YAxis tick={{ fontSize: 12 }} className="fill-muted-foreground" />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'hsl(var(--card))',
|
||||
border: '1px solid hsl(var(--border))',
|
||||
borderRadius: '0.5rem',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
formatter={tooltipFormatter ?? defaultFormatter}
|
||||
/>
|
||||
<Bar dataKey={dataKey} fill="hsl(var(--primary))" radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
66
apps/web/components/charts/price-trend-chart.tsx
Normal file
66
apps/web/components/charts/price-trend-chart.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
Legend,
|
||||
} from 'recharts';
|
||||
|
||||
interface PriceTrendChartProps {
|
||||
data: { period: string; 'Gia/m2': number; 'Tin dang': number }[];
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export function PriceTrendChart({ data, height = 350 }: PriceTrendChartProps) {
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={height}>
|
||||
<LineChart data={data} margin={{ top: 5, right: 30, left: 0, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
||||
<XAxis dataKey="period" tick={{ fontSize: 12 }} className="fill-muted-foreground" />
|
||||
<YAxis yAxisId="left" tick={{ fontSize: 12 }} className="fill-muted-foreground" />
|
||||
<YAxis
|
||||
yAxisId="right"
|
||||
orientation="right"
|
||||
tick={{ fontSize: 12 }}
|
||||
className="fill-muted-foreground"
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'hsl(var(--card))',
|
||||
border: '1px solid hsl(var(--border))',
|
||||
borderRadius: '0.5rem',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
formatter={(value, name) => [
|
||||
name === 'Gia/m2' ? `${value} tr/m2` : value,
|
||||
name,
|
||||
]}
|
||||
/>
|
||||
<Legend />
|
||||
<Line
|
||||
yAxisId="left"
|
||||
type="monotone"
|
||||
dataKey="Gia/m2"
|
||||
stroke="hsl(var(--primary))"
|
||||
strokeWidth={2}
|
||||
dot={{ r: 4 }}
|
||||
activeDot={{ r: 6 }}
|
||||
/>
|
||||
<Line
|
||||
yAxisId="right"
|
||||
type="monotone"
|
||||
dataKey="Tin dang"
|
||||
stroke="hsl(var(--muted-foreground))"
|
||||
strokeWidth={1}
|
||||
strokeDasharray="5 5"
|
||||
dot={{ r: 3 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user