Full 1:1 migration preserving all visuals, animations and behavior: - Nuxt 4 + Vue 3 (script setup, TypeScript strict) with SSG output - Tailwind CSS v4 with @tailwindcss/vite - Ant Design Vue 4 (dark theme, RTL, Persian locale) for design system - Pinia for state management (ui + contact stores) - motion-v (framer-motion port) for all animations - lucide-vue-next for icons - SEO improvements: og:image, canonical fix, real 404 page, JSON-LD services - JS bundle reduced from 302KB to 239KB gzip - Docker/nginx deployment preserved (out/ -> nginx) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import { fileURLToPath } from "node:url";
|
|
import tailwindcss from "@tailwindcss/vite";
|
|
import { siteConfig } from "./shared/config/site";
|
|
|
|
/** Unmatched path rendered through error.vue at build time and saved as out/404.html */
|
|
const NOT_FOUND_ROUTE = "/__not-found";
|
|
|
|
export default defineNuxtConfig({
|
|
compatibilityDate: "2026-10-01",
|
|
ssr: true,
|
|
devtools: { enabled: false },
|
|
modules: ["@pinia/nuxt"],
|
|
css: ["~/assets/css/main.css", "~/assets/css/antd-overrides.css"],
|
|
|
|
app: {
|
|
head: {
|
|
htmlAttrs: { lang: siteConfig.lang, dir: siteConfig.dir },
|
|
bodyAttrs: {
|
|
class: "relative min-h-screen bg-bg font-sans text-ink antialiased",
|
|
},
|
|
charset: "utf-8",
|
|
viewport: "width=device-width, initial-scale=1",
|
|
link: [
|
|
{
|
|
rel: "preload",
|
|
href: "/fonts/Vazirmatn-Variable.woff2",
|
|
as: "font",
|
|
type: "font/woff2",
|
|
crossorigin: "",
|
|
},
|
|
{
|
|
rel: "preload",
|
|
href: "/fonts/IBMPlexMono-Regular.woff2",
|
|
as: "font",
|
|
type: "font/woff2",
|
|
crossorigin: "",
|
|
},
|
|
{ rel: "icon", type: "image/svg+xml", href: siteConfig.favicon },
|
|
{ rel: "manifest", href: "/manifest.webmanifest" },
|
|
],
|
|
},
|
|
},
|
|
|
|
vite: {
|
|
plugins: [tailwindcss()],
|
|
ssr: { noExternal: ["ant-design-vue", "@ant-design/icons-vue"] },
|
|
},
|
|
|
|
// Static export (`npm run build` → ./out), served by nginx in Docker — same flow as the Next build.
|
|
nitro: {
|
|
output: { publicDir: fileURLToPath(new URL("./out", import.meta.url)) },
|
|
prerender: {
|
|
crawlLinks: false,
|
|
routes: ["/", "/robots.txt", "/sitemap.xml", "/manifest.webmanifest", NOT_FOUND_ROUTE],
|
|
},
|
|
hooks: {
|
|
// Nuxt's own /404.html is an empty SPA shell. Replace it with a server-rendered error.vue
|
|
// (real content + noindex in the static HTML) produced by prerendering an unmatched route.
|
|
"prerender:generate"(route) {
|
|
if (route.route === "/404.html") route.skip = true;
|
|
if (route.route === NOT_FOUND_ROUTE && route.error?.statusCode === 404) {
|
|
route.error = undefined;
|
|
route.fileName = "/404.html";
|
|
}
|
|
},
|
|
},
|
|
},
|
|
|
|
typescript: { strict: true },
|
|
});
|