Moved to next js ssr

This commit is contained in:
anibus
2026-08-15 14:46:19 +03:00
parent 4e4f798d85
commit 2184f1642c
37 changed files with 854 additions and 17862 deletions
+78
View File
@@ -0,0 +1,78 @@
import type { Metadata, Viewport } from 'next';
import '@/src/index.css';
import '@/src/App.css';
import '@/src/css/Unit.css';
import '@/src/css/Building.css';
import { ThemeProvider } from '@/src/context/ThemeContext';
import AppShell from '@/components/AppShell';
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://dow-wiki.example.com';
export const metadata: Metadata = {
metadataBase: new URL(siteUrl),
title: {
default: 'Dawn of War Wiki — юниты, расы и здания всех модов',
template: '%s — Dawn of War Wiki',
},
description:
'Dawn of War wiki. Unification mod wiki. Unification mod unit stats. Supported all popular mods.',
manifest: '/manifest.json',
applicationName: 'Dawn of War Wiki',
verification: {
google: 'Q4XMn2UDpL2xmK7iBfaWQpVN8EPy8oGxN-7HqSKapb0',
},
openGraph: {
type: 'website',
siteName: 'Dawn of War Wiki',
title: 'Dawn of War Wiki — юниты, расы и здания всех модов',
description:
'Dawn of War wiki. Unification mod wiki. Unification mod unit stats. Supported all popular mods.',
images: ['/logo512.png'],
},
twitter: {
card: 'summary_large_image',
},
robots: {
index: true,
follow: true,
},
};
export const viewport: Viewport = {
themeColor: '#000000',
width: 'device-width',
initialScale: 1,
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{/* Yandex.Metrika */}
<script
type="text/javascript"
dangerouslySetInnerHTML={{
__html:
"(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};m[i].l=1*new Date();for(var j=0;j<document.scripts.length;j++){if(document.scripts[j].src===r){return;}}k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})(window,document,\"script\",\"https://mc.yandex.ru/metrika/tag.js\",\"ym\");ym(99502794,\"init\",{clickmap:true,trackLinks:true,accurateTrackBounce:true,webvisor:true});",
}}
/>
<noscript>
<div>
<img
src="https://mc.yandex.ru/watch/99502794"
style={{ position: 'absolute', left: -9999 }}
alt=""
/>
</div>
</noscript>
<ThemeProvider>
<AppShell>{children}</AppShell>
</ThemeProvider>
</body>
</html>
);
}
+33
View File
@@ -0,0 +1,33 @@
import type { Metadata } from 'next';
import ModPageClient from '@/components/pages/ModPageClient';
import { getMod } from '@/lib/api-server';
export const dynamic = 'force-dynamic';
interface Props {
params: { modId: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const mod = await getMod(params.modId);
if (!mod) {
return { title: 'Mod' };
}
const title = `${mod.name} (${mod.version})`;
const description = `Dawn of War wiki. ${mod.name} mod. Units, races, buildings and stats.`;
return {
title,
description,
alternates: {
canonical: `/mod/${params.modId}`,
},
openGraph: {
title,
description,
},
};
}
export default function ModPage() {
return <ModPageClient />;
}
@@ -0,0 +1,37 @@
import type { Metadata } from 'next';
import BuildingPageClient from '@/components/pages/BuildingPageClient';
import { getBuilding, getMod } from '@/lib/api-server';
export const dynamic = 'force-dynamic';
interface Props {
params: { modId: string; raceId: string; buildingId: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const [mod, building] = await Promise.all([getMod(params.modId), getBuilding(params.buildingId)]);
if (!mod || !building) {
return { title: 'Building' };
}
let buildingName = building.name;
if (!buildingName) {
buildingName = building.filename.replaceAll('_', ' ').replace('.rgd', '');
}
const title = `${buildingName} — ${mod.name}`;
const description = `${buildingName} building in ${mod.name} mod. Stats, production, researches.`;
return {
title,
description,
alternates: {
canonical: `/mod/${params.modId}/race/${params.raceId}/building/${params.buildingId}`,
},
openGraph: {
title,
description,
},
};
}
export default function BuildingPage() {
return <BuildingPageClient />;
}
+33
View File
@@ -0,0 +1,33 @@
import type { Metadata } from 'next';
import RacePageClient from '@/components/pages/RacePageClient';
import { getMod, getRace } from '@/lib/api-server';
export const dynamic = 'force-dynamic';
interface Props {
params: { modId: string; raceId: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const [mod, race] = await Promise.all([getMod(params.modId), getRace(params.raceId)]);
if (!mod || !race) {
return { title: 'Race' };
}
const title = `${race.name} — ${mod.name}`;
const description = `${race.name} race in ${mod.name} mod. Units, buildings and stats.`;
return {
title,
description,
alternates: {
canonical: `/mod/${params.modId}/race/${params.raceId}`,
},
openGraph: {
title,
description,
},
};
}
export default function RacePage() {
return <RacePageClient />;
}
@@ -0,0 +1,35 @@
import type { Metadata } from 'next';
import UnitPageClient from '@/components/pages/UnitPageClient';
import { getMod, getUnit } from '@/lib/api-server';
export const dynamic = 'force-dynamic';
interface Props {
params: { modId: string; raceId: string; unitId: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const [mod, unit] = await Promise.all([getMod(params.modId), getUnit(params.unitId)]);
if (!mod || !unit) {
return { title: 'Unit' };
}
const title = `${unit.name} — ${mod.name}`;
const description = unit.description
? `${unit.name} unit in ${mod.name} mod. ${unit.description}`
: `${unit.name} unit in ${mod.name} mod. Stats, weapons, abilities.`;
return {
title,
description,
alternates: {
canonical: `/mod/${params.modId}/race/${params.raceId}/unit/${params.unitId}`,
},
openGraph: {
title,
description,
},
};
}
export default function UnitPage() {
return <UnitPageClient />;
}
+21
View File
@@ -0,0 +1,21 @@
import type { Metadata } from 'next';
import ModsPageClient from '@/components/pages/ModsPageClient';
import { getMods } from '@/lib/api-server';
import { IMod } from '@/src/types/Imod';
export const metadata: Metadata = {
title: 'Dawn of War Wiki — юниты, расы и здания всех модов',
description:
'Dawn of War wiki. Unification mod wiki. Unification mod unit stats. Supported all popular mods.',
alternates: {
canonical: '/',
},
};
export const dynamic = 'force-dynamic';
export default async function HomePage() {
const mods = (await getMods()) ?? [];
const visibleMods = mods.filter(mod => !mod.isHide);
return <ModsPageClient initialMods={visibleMods} />;
}
+12
View File
@@ -0,0 +1,12 @@
import type { MetadataRoute } from 'next';
import { SITE_URL } from '@/lib/api-server';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
},
sitemap: `${SITE_URL}/sitemap.xml`,
};
}
+78
View File
@@ -0,0 +1,78 @@
import type { MetadataRoute } from 'next';
import {
getBuildingsForMod,
getMods,
getUnitsForMod,
SITE_URL,
} from '@/lib/api-server';
export const dynamic = 'force-dynamic';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const mods = (await getMods()) ?? [];
const modEntries: MetadataRoute.Sitemap = mods.map((mod) => ({
url: `${SITE_URL}/mod/${mod.id}`,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.8,
}));
const entries: MetadataRoute.Sitemap = [...modEntries];
// Gather races/units/buildings per mod (limit parallelism)
const details = await Promise.all(
mods.slice(0, 20).map(async (mod) => {
const [units, buildings] = await Promise.all([
getUnitsForMod(mod.id),
getBuildingsForMod(mod.id),
]);
return { mod, units, buildings };
})
);
for (const { mod, units, buildings } of details) {
const raceIds = new Set<string>();
for (const unitGroup of units ?? []) raceIds.add(unitGroup.race.id);
for (const buildingGroup of buildings ?? []) raceIds.add(buildingGroup.race.id);
for (const raceId of raceIds) {
entries.push({
url: `${SITE_URL}/mod/${mod.id}/race/${raceId}`,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.7,
});
}
for (const unitGroup of units ?? []) {
for (const unit of [...unitGroup.infantry, ...unitGroup.support, ...unitGroup.tech]) {
entries.push({
url: `${SITE_URL}/mod/${mod.id}/race/${unitGroup.race.id}/unit/${unit.id}`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.5,
});
}
}
for (const buildingGroup of buildings ?? []) {
for (const building of [...buildingGroup.buildings, ...buildingGroup.buildingsAdvanced]) {
entries.push({
url: `${SITE_URL}/mod/${mod.id}/race/${buildingGroup.race.id}/building/${building.id}`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.5,
});
}
}
}
return [
{
url: SITE_URL,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
...entries,
];
}