Migrate from Vite to Next.js 15 with SSR
- Replace Vite + React Router with Next.js 15 App Router - Implement i18n with next-intl (URL-based: /de, /en, /sr) - Add SSR/SSG for all pages (48 static pages generated) - Setup Supabase SSR client for auth - Migrate all pages: Home, About, Portfolio, Blog, Contact, Login, Dashboard, Imprint, Privacy, Terms - Add Docker support with standalone output - Replace i18next with next-intl JSON translations - Use next/image for optimized images Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import { defineConfig, Plugin } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
import mdx from '@mdx-js/rollup';
|
||||
import remarkFrontmatter from 'remark-frontmatter';
|
||||
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
|
||||
|
||||
// Plugin to make CSS non-blocking and remove unnecessary preloads
|
||||
function asyncCssPlugin(): Plugin {
|
||||
return {
|
||||
name: 'async-css',
|
||||
enforce: 'post',
|
||||
transformIndexHtml(html) {
|
||||
// Convert blocking CSS to async loading
|
||||
html = html.replace(
|
||||
/<link rel="stylesheet" crossorigin href="(\/assets\/css\/[^"]+\.css)">/g,
|
||||
`<link rel="preload" href="$1" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="$1"></noscript>`
|
||||
);
|
||||
|
||||
// Remove modulepreload for non-critical chunks (animations, icons loaded later)
|
||||
html = html.replace(/<link rel="modulepreload" crossorigin href="\/assets\/js\/animations[^"]*\.js">\n?/g, '');
|
||||
html = html.replace(/<link rel="modulepreload" crossorigin href="\/assets\/js\/icons[^"]*\.js">\n?/g, '');
|
||||
|
||||
// Add high priority preload for entry script
|
||||
const mainScriptMatch = html.match(/<script type="module" crossorigin src="(\/assets\/js\/index-[^"]+\.js)">/);
|
||||
if (mainScriptMatch) {
|
||||
const preloadScript = `<link rel="preload" href="${mainScriptMatch[1]}" as="script" crossorigin fetchpriority="high">\n`;
|
||||
html = html.replace('</title>', '</title>\n ' + preloadScript);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
{
|
||||
...mdx({
|
||||
remarkPlugins: [
|
||||
remarkFrontmatter,
|
||||
[remarkMdxFrontmatter, { name: 'frontmatter' }]
|
||||
],
|
||||
rehypePlugins: [],
|
||||
providerImportSource: "@mdx-js/react"
|
||||
}),
|
||||
enforce: 'pre'
|
||||
},
|
||||
react({
|
||||
// React Plugin Optimierungen
|
||||
babel: {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
}
|
||||
}),
|
||||
asyncCssPlugin(),
|
||||
VitePWA({
|
||||
strategies: 'generateSW',
|
||||
registerType: 'autoUpdate',
|
||||
injectRegister: 'script-defer',
|
||||
manifest: {
|
||||
name: 'Damjan Savić - JTL Integration Expert',
|
||||
short_name: 'Damjan Savić',
|
||||
description: 'JTL Integration and E-commerce Solutions',
|
||||
theme_color: '#000000',
|
||||
background_color: '#000000',
|
||||
display: 'standalone',
|
||||
start_url: '/',
|
||||
scope: '/',
|
||||
icons: [
|
||||
{
|
||||
src: '/logo.svg',
|
||||
sizes: '192x192',
|
||||
type: 'image/svg+xml',
|
||||
purpose: 'any maskable'
|
||||
},
|
||||
{
|
||||
src: '/logo.svg',
|
||||
sizes: '512x512',
|
||||
type: 'image/svg+xml',
|
||||
purpose: 'any maskable'
|
||||
}
|
||||
]
|
||||
},
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,ico,png,svg,webp}'],
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'google-fonts-cache',
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'gstatic-fonts-cache',
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
urlPattern: /^https:\/\/www\.googletagmanager\.com\/.*/i,
|
||||
handler: 'NetworkOnly'
|
||||
}
|
||||
],
|
||||
cleanupOutdatedCaches: true
|
||||
},
|
||||
devOptions: {
|
||||
enabled: true,
|
||||
type: 'module'
|
||||
}
|
||||
})
|
||||
],
|
||||
optimizeDeps: {
|
||||
include: ['react/jsx-runtime'],
|
||||
exclude: ['@mdx-js/react']
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': '/src'
|
||||
}
|
||||
},
|
||||
css: {
|
||||
devSourcemap: true,
|
||||
preprocessorOptions: {
|
||||
postcss: {
|
||||
plugins: []
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
cssCodeSplit: true,
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
drop_debugger: true
|
||||
}
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
|
||||
'mdx-vendor': ['@mdx-js/react'],
|
||||
'i18n-vendor': ['i18next', 'react-i18next'],
|
||||
'icons': ['lucide-react'],
|
||||
'animations': ['framer-motion']
|
||||
},
|
||||
// Optimierte Asset-Dateinamen
|
||||
assetFileNames: (assetInfo) => {
|
||||
let extType = assetInfo.name.split('.').at(-1);
|
||||
if (/webp|png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
|
||||
extType = 'img';
|
||||
}
|
||||
return `assets/${extType}/[name]-[hash][extname]`;
|
||||
},
|
||||
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||
entryFileNames: 'assets/js/[name]-[hash].js',
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
hmr: {
|
||||
overlay: false
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user