Implement comprehensive homepage improvements and SEO optimization
Major homepage and SEO enhancements based on optimization document: **Homepage Content Updates:** - Updated H1 titles with SEO-optimized text for both RO/EN - Enhanced hero descriptions with targeted keywords - Improved feature descriptions for better clarity - Updated daily verse section with keyword-rich titles - Added new footer description with SEO focus **SEO Implementation:** - Added dynamic metadata generation with locale-specific SEO - Implemented Open Graph tags for social media sharing - Added Twitter Card metadata for enhanced sharing - Integrated Schema.org JSON-LD structured data - Set up hreflang tags for international SEO - Added canonical URLs to prevent duplicate content - Included targeted keywords for both languages **Technical Improvements:** - Migrated from Docker to PM2 deployment - Removed Docker files and updated deployment scripts - Updated README with PM2 instructions - Fixed console log cleanup for production - Added proper favicon with Next.js app directory - Increased memory limit to 4GB for better performance - Updated port configuration to 0.0.0.0:3010 - Set Romanian (/ro) as default locale with proper redirects **Translation Updates:** - Enhanced Romanian translations with SEO-optimized content - Updated English translations with matching SEO improvements - Added new 'seo' namespace for metadata translations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
56
Dockerfile
56
Dockerfile
@@ -1,56 +0,0 @@
|
|||||||
FROM node:20-alpine AS base
|
|
||||||
|
|
||||||
# Install dependencies only when needed
|
|
||||||
FROM base AS deps
|
|
||||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
|
||||||
RUN apk add --no-cache libc6-compat
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install dependencies based on the preferred package manager
|
|
||||||
COPY package.json package-lock.json* ./
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Rebuild the source code only when needed
|
|
||||||
FROM base AS builder
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Next.js collects completely anonymous telemetry data about general usage.
|
|
||||||
# Learn more here: https://nextjs.org/telemetry
|
|
||||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
|
||||||
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# Production image, copy all the files and run next
|
|
||||||
FROM base AS runner
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
|
||||||
|
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
|
||||||
RUN adduser --system --uid 1001 nextjs
|
|
||||||
|
|
||||||
COPY --from=builder /app/public ./public
|
|
||||||
|
|
||||||
# Set the correct permission for prerender cache
|
|
||||||
RUN mkdir .next
|
|
||||||
RUN chown nextjs:nodejs .next
|
|
||||||
|
|
||||||
# Automatically leverage output traces to reduce image size
|
|
||||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
||||||
|
|
||||||
USER nextjs
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
ENV PORT=3000
|
|
||||||
|
|
||||||
# server.js is created by next build from the standalone output
|
|
||||||
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
|
|
||||||
CMD HOSTNAME="0.0.0.0" node server.js
|
|
||||||
41
README.md
41
README.md
@@ -54,12 +54,12 @@ O aplicație web completă pentru studiul Bibliei cu capabilități de chat AI
|
|||||||
- **AI**: Azure OpenAI API cu fallback la Ollama
|
- **AI**: Azure OpenAI API cu fallback la Ollama
|
||||||
- **Security**: JWT, bcrypt, rate limiting, input validation
|
- **Security**: JWT, bcrypt, rate limiting, input validation
|
||||||
- **Testing**: Jest, React Testing Library, TypeScript
|
- **Testing**: Jest, React Testing Library, TypeScript
|
||||||
- **DevOps**: Docker, Docker Compose, Nginx, SSL support
|
- **DevOps**: PM2, Nginx, SSL support
|
||||||
- **Performance**: Caching, indexing, optimization scripts
|
- **Performance**: Caching, indexing, optimization scripts
|
||||||
|
|
||||||
## Instalare Rapidă
|
## Instalare Rapidă
|
||||||
|
|
||||||
### Folosind Docker (Recomandat)
|
### Folosind PM2 (Recomandat)
|
||||||
|
|
||||||
1. Clonează repository-ul:
|
1. Clonează repository-ul:
|
||||||
```bash
|
```bash
|
||||||
@@ -83,18 +83,25 @@ JWT_SECRET=your-secure-jwt-secret
|
|||||||
NEXTAUTH_SECRET=your-secure-nextauth-secret
|
NEXTAUTH_SECRET=your-secure-nextauth-secret
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Pornește aplicația:
|
4. Instalează dependențele și construiește aplicația:
|
||||||
```bash
|
```bash
|
||||||
docker-compose up -d
|
npm ci
|
||||||
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Rulează migrațiile și importă datele biblice:
|
5. Rulează migrațiile și importă datele biblice:
|
||||||
```bash
|
```bash
|
||||||
docker-compose exec app npx prisma migrate deploy
|
npx prisma migrate deploy
|
||||||
docker-compose exec app npm run import-bible
|
npx prisma generate
|
||||||
|
npm run import-bible
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Accesează aplicația la: http://localhost:3000
|
6. Pornește aplicația cu PM2:
|
||||||
|
```bash
|
||||||
|
pm2 start ecosystem.config.js --env production
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Accesează aplicația la: http://localhost:3000
|
||||||
|
|
||||||
### Instalare Manuală
|
### Instalare Manuală
|
||||||
|
|
||||||
@@ -149,7 +156,7 @@ npm run dev
|
|||||||
│ └── db.ts # Conexiunea la baza de date
|
│ └── db.ts # Conexiunea la baza de date
|
||||||
├── prisma/ # Schema și migrații Prisma
|
├── prisma/ # Schema și migrații Prisma
|
||||||
├── scripts/ # Scripturi de utilitate
|
├── scripts/ # Scripturi de utilitate
|
||||||
└── docker/ # Configurații Docker
|
└── ecosystem.config.js # Configurație PM2
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configurare AI
|
## Configurare AI
|
||||||
@@ -171,12 +178,20 @@ Pentru rularea locală de modele AI:
|
|||||||
|
|
||||||
## Deployment în Producție
|
## Deployment în Producție
|
||||||
|
|
||||||
### Folosind Docker
|
### Folosind PM2
|
||||||
|
|
||||||
1. Copiază `.env.example` la `.env.production` și configurează-l
|
1. Copiază `.env.example` la `.env` și configurează-l pentru producție
|
||||||
2. Construiește și pornește serviciile:
|
2. Rulează scriptul de deployment:
|
||||||
```bash
|
```bash
|
||||||
docker-compose -f docker-compose.prod.yml up -d
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Sau manual:
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
pm2 restart ghidul-biblic || pm2 start ecosystem.config.js --env production
|
||||||
|
pm2 save
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configurare SSL
|
### Configurare SSL
|
||||||
@@ -194,7 +209,7 @@ sudo certbot --nginx -d yourdomain.com
|
|||||||
## Monitorizare
|
## Monitorizare
|
||||||
|
|
||||||
- **Health Check**: `/api/health`
|
- **Health Check**: `/api/health`
|
||||||
- **Logs**: `docker-compose logs -f app`
|
- **Logs**: `pm2 logs ghidul-biblic`
|
||||||
- **Metrici**: Implementate prin endpoint-uri dedicate
|
- **Metrici**: Implementate prin endpoint-uri dedicate
|
||||||
|
|
||||||
## Contribuții
|
## Contribuții
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import '../globals.css'
|
import '../globals.css'
|
||||||
import type { Metadata } from 'next'
|
import type { Metadata } from 'next'
|
||||||
import { NextIntlClientProvider } from 'next-intl'
|
import { NextIntlClientProvider } from 'next-intl'
|
||||||
import { getMessages } from 'next-intl/server'
|
import { getMessages, getTranslations } from 'next-intl/server'
|
||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import { MuiThemeProvider } from '@/components/providers/theme-provider'
|
import { MuiThemeProvider } from '@/components/providers/theme-provider'
|
||||||
import { AuthProvider } from '@/components/auth/auth-provider'
|
import { AuthProvider } from '@/components/auth/auth-provider'
|
||||||
@@ -9,9 +9,66 @@ import { Navigation } from '@/components/layout/navigation'
|
|||||||
import FloatingChat from '@/components/chat/floating-chat'
|
import FloatingChat from '@/components/chat/floating-chat'
|
||||||
import { merriweather, lato } from '@/lib/fonts'
|
import { merriweather, lato } from '@/lib/fonts'
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
|
||||||
title: 'Ghid Biblic - Biblical Guide',
|
const { locale } = await params
|
||||||
description: 'A comprehensive Bible study application with AI chat capabilities',
|
const t = await getTranslations({ locale, namespace: 'seo' })
|
||||||
|
|
||||||
|
const currentUrl = locale === 'ro' ? 'https://ghidulbiblic.ro/ro/' : 'https://ghidulbiblic.ro/en/'
|
||||||
|
const alternateUrl = locale === 'ro' ? 'https://ghidulbiblic.ro/en/' : 'https://ghidulbiblic.ro/ro/'
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('title'),
|
||||||
|
description: t('description'),
|
||||||
|
keywords: t('keywords'),
|
||||||
|
alternates: {
|
||||||
|
canonical: currentUrl,
|
||||||
|
languages: {
|
||||||
|
'ro': 'https://ghidulbiblic.ro/ro/',
|
||||||
|
'en': 'https://ghidulbiblic.ro/en/',
|
||||||
|
'x-default': 'https://ghidulbiblic.ro/'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: t('ogTitle'),
|
||||||
|
description: t('ogDescription'),
|
||||||
|
url: currentUrl,
|
||||||
|
siteName: locale === 'ro' ? 'Ghid Biblic' : 'Biblical Guide',
|
||||||
|
locale: locale,
|
||||||
|
type: 'website',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: `https://ghidulbiblic.ro/og-image-${locale}.jpg`,
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: t('ogTitle'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: 'summary_large_image',
|
||||||
|
site: '@ghidbiblic',
|
||||||
|
title: t('twitterTitle'),
|
||||||
|
description: t('twitterDescription'),
|
||||||
|
images: [`https://ghidulbiblic.ro/og-image-${locale}.jpg`],
|
||||||
|
},
|
||||||
|
other: {
|
||||||
|
'application/ld+json': JSON.stringify({
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "MobileApplication",
|
||||||
|
"name": locale === 'ro' ? "Ghid Biblic" : "Biblical Guide",
|
||||||
|
"url": "https://ghidulbiblic.ro",
|
||||||
|
"description": t('description'),
|
||||||
|
"applicationCategory": "EducationApplication",
|
||||||
|
"operatingSystem": "iOS, Android, Web",
|
||||||
|
"inLanguage": [locale],
|
||||||
|
"offers": {
|
||||||
|
"@type": "Offer",
|
||||||
|
"price": "0",
|
||||||
|
"priceCurrency": "USD"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export default function Home() {
|
|||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const t = useTranslations('home')
|
const t = useTranslations('home')
|
||||||
|
const tSeo = useTranslations('seo')
|
||||||
const locale = useLocale()
|
const locale = useLocale()
|
||||||
const [userCount, setUserCount] = useState(2847)
|
const [userCount, setUserCount] = useState(2847)
|
||||||
const [expandedFaq, setExpandedFaq] = useState<string | false>(false)
|
const [expandedFaq, setExpandedFaq] = useState<string | false>(false)
|
||||||
@@ -595,7 +596,7 @@ export default function Home() {
|
|||||||
{t('footer.brand')}
|
{t('footer.brand')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="grey.400" sx={{ maxWidth: 300 }}>
|
<Typography variant="body2" color="grey.400" sx={{ maxWidth: 300 }}>
|
||||||
{t('footer.description')}
|
{tSeo('footer')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
@tailwind base;
|
@import "tailwindcss";
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--foreground-rgb: 0, 0, 0;
|
--foreground-rgb: 0, 0, 0;
|
||||||
|
|||||||
32
app/icon.tsx
Normal file
32
app/icon.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { ImageResponse } from 'next/og'
|
||||||
|
|
||||||
|
export const size = {
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
}
|
||||||
|
export const contentType = 'image/png'
|
||||||
|
|
||||||
|
export default function Icon() {
|
||||||
|
return new ImageResponse(
|
||||||
|
(
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
background: '#333',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'white',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
B
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
...size,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -38,7 +38,6 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
|
|
||||||
// Check if token is expired before making request
|
// Check if token is expired before making request
|
||||||
if (isTokenExpired(token)) {
|
if (isTokenExpired(token)) {
|
||||||
console.log('Token expired in refreshUser, clearing auth state')
|
|
||||||
localStorage.removeItem('authToken')
|
localStorage.removeItem('authToken')
|
||||||
setUser(null)
|
setUser(null)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
@@ -62,14 +61,6 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
setUser(data.user)
|
setUser(data.user)
|
||||||
} else {
|
} else {
|
||||||
// Token is invalid or expired, get error details
|
|
||||||
try {
|
|
||||||
const errorData = await response.json()
|
|
||||||
console.log('Server returned 401 error:', errorData)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Server returned 401 without JSON body, status:', response.status)
|
|
||||||
}
|
|
||||||
console.log('Token expired or invalid, clearing auth state')
|
|
||||||
localStorage.removeItem('authToken')
|
localStorage.removeItem('authToken')
|
||||||
setUser(null)
|
setUser(null)
|
||||||
}
|
}
|
||||||
@@ -83,81 +74,18 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug database schema
|
|
||||||
const debugSchema = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/debug/schema')
|
|
||||||
const debug = await response.json()
|
|
||||||
console.log('Database schema info:', debug)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Schema debug failed:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug user lookup
|
|
||||||
const debugUser = async (userId: string) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/debug/user', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ userId })
|
|
||||||
})
|
|
||||||
const debug = await response.json()
|
|
||||||
console.log('User debug info:', debug)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('User debug failed:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug token validation
|
|
||||||
const debugToken = async (token: string) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/debug/token', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ token })
|
|
||||||
})
|
|
||||||
const debug = await response.json()
|
|
||||||
console.log('Token debug info:', debug)
|
|
||||||
|
|
||||||
// Log more details about the token payload
|
|
||||||
if (debug.payload) {
|
|
||||||
console.log('Token payload:', debug.payload)
|
|
||||||
|
|
||||||
// Debug user lookup
|
|
||||||
if (debug.payload.userId) {
|
|
||||||
debugUser(debug.payload.userId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (debug.verificationResult) {
|
|
||||||
console.log('Verification result:', debug.verificationResult)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Token debug failed:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear expired tokens and sync state immediately on mount
|
// Clear expired tokens and sync state immediately on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const token = localStorage.getItem('authToken')
|
const token = localStorage.getItem('authToken')
|
||||||
console.log('Auth mount check - token exists:', !!token)
|
|
||||||
if (token) {
|
if (token) {
|
||||||
console.log('Token preview:', token.substring(0, 50) + '...')
|
|
||||||
|
|
||||||
// Debug the database schema and token on server side
|
|
||||||
debugSchema()
|
|
||||||
debugToken(token)
|
|
||||||
|
|
||||||
const expired = isTokenExpired(token)
|
const expired = isTokenExpired(token)
|
||||||
console.log('Token expired:', expired)
|
|
||||||
if (expired) {
|
if (expired) {
|
||||||
console.log('Clearing expired token and user state on mount')
|
|
||||||
localStorage.removeItem('authToken')
|
localStorage.removeItem('authToken')
|
||||||
setUser(null)
|
setUser(null)
|
||||||
}
|
}
|
||||||
} else if (user) {
|
} else if (user) {
|
||||||
console.log('No token but user exists in store, clearing user state')
|
|
||||||
setUser(null)
|
setUser(null)
|
||||||
}
|
}
|
||||||
clearExpiredToken()
|
clearExpiredToken()
|
||||||
@@ -168,23 +96,19 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initialized && typeof window !== 'undefined') {
|
if (!initialized && typeof window !== 'undefined') {
|
||||||
const token = localStorage.getItem('authToken')
|
const token = localStorage.getItem('authToken')
|
||||||
console.log('Initialization flow - token exists:', !!token)
|
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
// Check if token is expired before making request
|
// Check if token is expired before making request
|
||||||
if (isTokenExpired(token)) {
|
if (isTokenExpired(token)) {
|
||||||
console.log('Token expired during initialization, clearing auth state')
|
|
||||||
localStorage.removeItem('authToken')
|
localStorage.removeItem('authToken')
|
||||||
setUser(null)
|
setUser(null)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
} else {
|
} else {
|
||||||
console.log('Token is valid, calling refreshUser()')
|
|
||||||
// Token appears valid, try to refresh user data
|
// Token appears valid, try to refresh user data
|
||||||
// refreshUser will handle server-side validation failures
|
// refreshUser will handle server-side validation failures
|
||||||
refreshUser()
|
refreshUser()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('No token found, clearing user state')
|
|
||||||
// No token, clear any stale user data
|
// No token, clear any stale user data
|
||||||
setUser(null)
|
setUser(null)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ export default function FloatingChat() {
|
|||||||
const checkAuthStatus = useCallback(async () => {
|
const checkAuthStatus = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('authToken')
|
const token = localStorage.getItem('authToken')
|
||||||
console.log('Chat - checkAuthStatus: token from localStorage:', token ? 'present' : 'null')
|
|
||||||
if (token) {
|
if (token) {
|
||||||
// Verify token with the server
|
// Verify token with the server
|
||||||
const response = await fetch('/api/auth/me', {
|
const response = await fetch('/api/auth/me', {
|
||||||
@@ -146,20 +145,16 @@ export default function FloatingChat() {
|
|||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log('Chat - auth verification response:', response.ok)
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setAuthToken(token)
|
setAuthToken(token)
|
||||||
setIsAuthenticated(true)
|
setIsAuthenticated(true)
|
||||||
console.log('Chat - Auth state set: authenticated')
|
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem('authToken')
|
localStorage.removeItem('authToken')
|
||||||
setIsAuthenticated(false)
|
setIsAuthenticated(false)
|
||||||
setAuthToken(null)
|
setAuthToken(null)
|
||||||
console.log('Chat - Auth verification failed, cleared state')
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Chat - No token in localStorage')
|
|
||||||
setIsAuthenticated(false)
|
setIsAuthenticated(false)
|
||||||
setAuthToken(null)
|
setAuthToken(null)
|
||||||
}
|
}
|
||||||
|
|||||||
55
deploy.sh
55
deploy.sh
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Deploy script for Biblical Guide production server
|
# Deploy script for Biblical Guide production server
|
||||||
# Fetches latest code from production branch and deploys
|
# Fetches latest code from production branch and deploys with PM2
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ echo "🚀 Starting deployment..."
|
|||||||
REPO_URL="https://git.noru1.ro/andrei/ghidul-biblic.git"
|
REPO_URL="https://git.noru1.ro/andrei/ghidul-biblic.git"
|
||||||
BRANCH="production"
|
BRANCH="production"
|
||||||
APP_NAME="ghidul-biblic"
|
APP_NAME="ghidul-biblic"
|
||||||
|
PORT="3010"
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -60,10 +61,6 @@ fi
|
|||||||
|
|
||||||
print_success "Environment variables validated"
|
print_success "Environment variables validated"
|
||||||
|
|
||||||
# Stop existing containers
|
|
||||||
print_status "Stopping existing containers..."
|
|
||||||
docker compose down || true
|
|
||||||
|
|
||||||
# Fetch latest code from production branch
|
# Fetch latest code from production branch
|
||||||
print_status "Fetching latest code from $BRANCH branch..."
|
print_status "Fetching latest code from $BRANCH branch..."
|
||||||
git fetch origin $BRANCH
|
git fetch origin $BRANCH
|
||||||
@@ -76,40 +73,54 @@ CURRENT_COMMIT=$(git rev-parse --short HEAD)
|
|||||||
COMMIT_MSG=$(git log -1 --pretty=format:"%s")
|
COMMIT_MSG=$(git log -1 --pretty=format:"%s")
|
||||||
print_status "Current commit: $CURRENT_COMMIT - $COMMIT_MSG"
|
print_status "Current commit: $CURRENT_COMMIT - $COMMIT_MSG"
|
||||||
|
|
||||||
# Build and start the application
|
# Install dependencies if package.json changed
|
||||||
print_status "Building and starting application..."
|
if git diff --name-only HEAD~1 HEAD | grep -q "package.json\|package-lock.json"; then
|
||||||
docker compose up --build -d
|
print_status "Dependencies changed, installing..."
|
||||||
|
npm ci
|
||||||
|
print_success "Dependencies installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
print_status "Building application..."
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
print_success "Application built successfully"
|
||||||
|
|
||||||
|
# Restart with PM2
|
||||||
|
print_status "Restarting application with PM2..."
|
||||||
|
pm2 restart $APP_NAME || pm2 start ecosystem.config.js --env production
|
||||||
|
|
||||||
|
# Save PM2 configuration
|
||||||
|
pm2 save
|
||||||
|
|
||||||
|
print_success "Application restarted with PM2"
|
||||||
|
|
||||||
# Wait for application to be ready
|
# Wait for application to be ready
|
||||||
print_status "Waiting for application to start..."
|
print_status "Waiting for application to start..."
|
||||||
sleep 10
|
sleep 5
|
||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
print_status "Performing health check..."
|
print_status "Performing health check..."
|
||||||
for i in {1..30}; do
|
for i in {1..30}; do
|
||||||
if curl -f http://localhost:3010/api/health >/dev/null 2>&1; then
|
if curl -f http://localhost:$PORT/api/health >/dev/null 2>&1; then
|
||||||
print_success "Application is healthy and running on port 3010"
|
print_success "Application is healthy and running on port $PORT"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $i -eq 30 ]; then
|
if [ $i -eq 30 ]; then
|
||||||
print_error "Health check failed after 30 attempts"
|
print_error "Health check failed after 30 attempts"
|
||||||
print_status "Showing container logs:"
|
print_status "Showing PM2 logs:"
|
||||||
docker compose logs --tail=50
|
pm2 logs $APP_NAME --lines 20
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
# Show running containers
|
# Show PM2 status
|
||||||
print_status "Running containers:"
|
print_status "PM2 Status:"
|
||||||
docker compose ps
|
pm2 status
|
||||||
|
|
||||||
# Cleanup old images (keep last 3)
|
|
||||||
print_status "Cleaning up old Docker images..."
|
|
||||||
docker image prune -f >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
print_success "🎉 Deployment completed successfully!"
|
print_success "🎉 Deployment completed successfully!"
|
||||||
print_status "Application is now running at: http://localhost:3010"
|
print_status "Application is now running at: http://localhost:$PORT"
|
||||||
print_status "API health endpoint: http://localhost:3010/api/health"
|
print_status "API health endpoint: http://localhost:$PORT/api/health"
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
services:
|
|
||||||
app:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "3010:3000"
|
|
||||||
environment:
|
|
||||||
DATABASE_URL: ${DATABASE_URL}
|
|
||||||
AZURE_OPENAI_KEY: ${AZURE_OPENAI_KEY}
|
|
||||||
AZURE_OPENAI_ENDPOINT: ${AZURE_OPENAI_ENDPOINT}
|
|
||||||
AZURE_OPENAI_DEPLOYMENT: ${AZURE_OPENAI_DEPLOYMENT}
|
|
||||||
OLLAMA_API_URL: ${OLLAMA_API_URL}
|
|
||||||
JWT_SECRET: ${JWT_SECRET}
|
|
||||||
NEXTAUTH_URL: ${NEXTAUTH_URL}
|
|
||||||
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
|
|
||||||
NODE_ENV: production
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
28
ecosystem.config.js
Normal file
28
ecosystem.config.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'ghidul-biblic',
|
||||||
|
script: 'npm',
|
||||||
|
args: 'start',
|
||||||
|
cwd: '/root/ghidul-biblic',
|
||||||
|
instances: 1,
|
||||||
|
autorestart: true,
|
||||||
|
watch: false,
|
||||||
|
max_memory_restart: '4G',
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 3010,
|
||||||
|
HOSTNAME: '0.0.0.0',
|
||||||
|
},
|
||||||
|
env_production: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 3010,
|
||||||
|
HOSTNAME: '0.0.0.0',
|
||||||
|
},
|
||||||
|
error_file: './logs/err.log',
|
||||||
|
out_file: './logs/out.log',
|
||||||
|
log_file: './logs/combined.log',
|
||||||
|
time: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
13
i18n.ts
13
i18n.ts
@@ -1,14 +1,21 @@
|
|||||||
import {getRequestConfig} from 'next-intl/server';
|
import {getRequestConfig} from 'next-intl/server';
|
||||||
|
import ro from './messages/ro.json';
|
||||||
|
import en from './messages/en.json';
|
||||||
|
|
||||||
// Can be imported from a shared config
|
// Can be imported from a shared config
|
||||||
export const locales = ['ro', 'en'];
|
export const locales = ['ro', 'en'] as const;
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
ro,
|
||||||
|
en
|
||||||
|
} as const;
|
||||||
|
|
||||||
export default getRequestConfig(async ({locale}) => {
|
export default getRequestConfig(async ({locale}) => {
|
||||||
// Ensure locale has a value, default to 'ro' if undefined
|
// Ensure locale has a value, default to 'ro' if undefined
|
||||||
const validLocale = locale || 'ro';
|
const validLocale = (locale || 'ro') as keyof typeof messages;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale: validLocale,
|
locale: validLocale,
|
||||||
messages: (await import(`./messages/${validLocale}.json`)).default
|
messages: messages[validLocale]
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -2,16 +2,13 @@ export function isTokenExpired(token: string): boolean {
|
|||||||
try {
|
try {
|
||||||
const payload = JSON.parse(atob(token.split('.')[1])) as { exp?: number }
|
const payload = JSON.parse(atob(token.split('.')[1])) as { exp?: number }
|
||||||
if (!payload || !payload.exp) {
|
if (!payload || !payload.exp) {
|
||||||
console.log('Token has no expiration data')
|
return true
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTime = Math.floor(Date.now() / 1000)
|
const currentTime = Math.floor(Date.now() / 1000)
|
||||||
const isExpired = payload.exp < currentTime
|
const isExpired = payload.exp < currentTime
|
||||||
console.log(`Token expiration check: exp=${payload.exp}, now=${currentTime}, expired=${isExpired}`)
|
|
||||||
return isExpired
|
return isExpired
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Token validation error:', error)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,11 +16,8 @@ export function isTokenExpired(token: string): boolean {
|
|||||||
export function clearExpiredToken(): void {
|
export function clearExpiredToken(): void {
|
||||||
const token = localStorage.getItem('authToken')
|
const token = localStorage.getItem('authToken')
|
||||||
if (token && isTokenExpired(token)) {
|
if (token && isTokenExpired(token)) {
|
||||||
console.log('Clearing expired token from localStorage')
|
|
||||||
localStorage.removeItem('authToken')
|
localStorage.removeItem('authToken')
|
||||||
} else if (token) {
|
} else if (token) {
|
||||||
console.log('Token exists and is valid')
|
|
||||||
} else {
|
} else {
|
||||||
console.log('No token in localStorage')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,33 +33,33 @@
|
|||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"hero": {
|
"hero": {
|
||||||
"title": "Biblical Guide",
|
"title": "Biblical Guide – Online Bible Study with AI Chat, Daily Verses, and Prayer Community",
|
||||||
"subtitle": "Explore Scripture with artificial intelligence",
|
"subtitle": "Online Bible Study with AI assistance",
|
||||||
"description": "A modern platform for Bible study, with intelligent AI chat, advanced search, and a prayer community that supports you on your spiritual journey.",
|
"description": "Biblical Guide is an online Bible study app. Read Scripture, ask questions with AI-powered chat, search verses instantly, and join a global prayer community that supports your spiritual growth.",
|
||||||
"cta": {
|
"cta": {
|
||||||
"readBible": "Start reading",
|
"readBible": "Start reading",
|
||||||
"askAI": "Ask AI"
|
"askAI": "Try it free now – AI Bible chat"
|
||||||
},
|
},
|
||||||
"liveCounter": "Join thousands of believers studying God's word right now"
|
"liveCounter": "Join thousands of believers who use Biblical Guide to study, understand, and apply God's Word in their everyday lives"
|
||||||
},
|
},
|
||||||
"features": {
|
"features": {
|
||||||
"title": "Discover the features",
|
"title": "Discover the features",
|
||||||
"subtitle": "Everything you need for a complete Bible study experience",
|
"subtitle": "Everything you need for a complete Bible study experience",
|
||||||
"bible": {
|
"bible": {
|
||||||
"title": "Read the Bible",
|
"title": "Read the Bible online",
|
||||||
"description": "Explore Scripture with a modern and easy-to-use interface"
|
"description": "access all 66 books with a modern and intuitive interface"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "AI Chat",
|
"title": "AI Bible Chat",
|
||||||
"description": "Ask questions about Scripture and receive clear answers"
|
"description": "ask Scripture questions and get clear, accurate answers"
|
||||||
},
|
},
|
||||||
"prayers": {
|
"prayers": {
|
||||||
"title": "Prayers",
|
"title": "Prayer Community",
|
||||||
"description": "Share prayers and pray together with the community"
|
"description": "share requests and join others in prayer"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"title": "Search",
|
"title": "Verse Search",
|
||||||
"description": "Search for verses and passages throughout Scripture"
|
"description": "quickly find verses, keywords, and topics across the Bible"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
"tryButton": "Try it yourself"
|
"tryButton": "Try it yourself"
|
||||||
},
|
},
|
||||||
"dailyVerse": {
|
"dailyVerse": {
|
||||||
"title": "Today's Verse",
|
"title": "Daily Bible Verse – receive encouragement from Scripture every day, straight to your inbox",
|
||||||
"date": "January 15, 2024",
|
"date": "January 15, 2024",
|
||||||
"verse": "For I know the plans I have for you, declares the Lord, plans to prosper you and not to harm you, plans to give you hope and a future.",
|
"verse": "For I know the plans I have for you, declares the Lord, plans to prosper you and not to harm you, plans to give you hope and a future.",
|
||||||
"reference": "Jeremiah 29:11",
|
"reference": "Jeremiah 29:11",
|
||||||
@@ -481,5 +481,15 @@
|
|||||||
"back": "Back",
|
"back": "Back",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"previous": "Previous"
|
"previous": "Previous"
|
||||||
|
},
|
||||||
|
"seo": {
|
||||||
|
"title": "Biblical Guide – Online Bible Study with AI, Daily Verses & Prayer Community",
|
||||||
|
"description": "Biblical Guide is an online Bible study app with AI-powered chat, instant verse search, and a global prayer community. Get daily Bible verses and Scripture-based answers to your questions.",
|
||||||
|
"keywords": "online Bible study, AI Bible chat, daily Bible verse, Bible study app, prayer community, read the Bible online, verse search, Scripture study",
|
||||||
|
"ogTitle": "Biblical Guide – Online Bible Study with AI",
|
||||||
|
"ogDescription": "Read the Bible online, ask questions with AI chat, and join a prayer community. Get daily Bible verses in your inbox.",
|
||||||
|
"twitterTitle": "Biblical Guide – Online Bible Study with AI",
|
||||||
|
"twitterDescription": "Online Bible study app with AI chat, daily verses, and prayer community.",
|
||||||
|
"footer": "Biblical Guide – online Bible study app with AI chat, daily verses, and prayer community."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,33 +33,33 @@
|
|||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"hero": {
|
"hero": {
|
||||||
"title": "Ghid Biblic",
|
"title": "Ghid Biblic – Studiu biblic online cu AI, versete zilnice și comunitate de rugăciune",
|
||||||
"subtitle": "Explorează Scriptura cu ajutorul inteligenței artificiale",
|
"subtitle": "Studiu biblic online cu asistență AI",
|
||||||
"description": "O platformă modernă pentru studiul Bibliei, cu chat AI inteligent, căutare avansată și o comunitate de rugăciune care te sprijină în călătoria ta spirituală.",
|
"description": "Ghid Biblic este o aplicație de studiu biblic online. Citește Scriptura, pune întrebări cu ajutorul chatului AI, caută versete rapid și alătură-te unei comunități de rugăciune care te sprijină zilnic.",
|
||||||
"cta": {
|
"cta": {
|
||||||
"readBible": "Începe să citești",
|
"readBible": "Începe să citești",
|
||||||
"askAI": "Întreabă AI"
|
"askAI": "Încearcă acum gratuit - Chat AI"
|
||||||
},
|
},
|
||||||
"liveCounter": "Alătură-te la mii de credincioși care studiază Cuvântul lui Dumnezeu chiar acum"
|
"liveCounter": "Alătură-te miilor de credincioși care folosesc Ghid Biblic pentru a înțelege și aplica Cuvântul lui Dumnezeu în viața de zi cu zi"
|
||||||
},
|
},
|
||||||
"features": {
|
"features": {
|
||||||
"title": "Descoperă funcționalitățile",
|
"title": "Descoperă funcționalitățile",
|
||||||
"subtitle": "Totul de ce ai nevoie pentru o experiență completă de studiu biblic",
|
"subtitle": "Totul de ce ai nevoie pentru o experiență completă de studiu biblic",
|
||||||
"bible": {
|
"bible": {
|
||||||
"title": "Citește Biblia",
|
"title": "Citește Biblia online",
|
||||||
"description": "Explorează Scriptura cu o interfață modernă și ușor de folosit"
|
"description": "toate cele 66 de cărți biblice într-o interfață modernă și intuitivă"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Chat cu AI",
|
"title": "Chat AI biblic",
|
||||||
"description": "Pune întrebări despre Scriptură și primește răspunsuri clare"
|
"description": "întreabă Scriptura și primește răspunsuri clare, fundamentate pe versete"
|
||||||
},
|
},
|
||||||
"prayers": {
|
"prayers": {
|
||||||
"title": "Rugăciuni",
|
"title": "Comunitate de rugăciune",
|
||||||
"description": "Partajează rugăciuni și roagă-te împreună cu comunitatea"
|
"description": "trimite și primește cereri de rugăciune"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"title": "Căutare",
|
"title": "Căutare versete",
|
||||||
"description": "Caută versete și pasaje din întreaga Scriptură"
|
"description": "găsește rapid pasaje, cuvinte cheie și teme biblice"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
"tryButton": "Încearcă și tu"
|
"tryButton": "Încearcă și tu"
|
||||||
},
|
},
|
||||||
"dailyVerse": {
|
"dailyVerse": {
|
||||||
"title": "Versetul de Astăzi",
|
"title": "Versetul biblic al zilei – primește zilnic inspirație din Scriptură direct în inbox",
|
||||||
"date": "15 ianuarie 2024",
|
"date": "15 ianuarie 2024",
|
||||||
"verse": "Căci Eu știu gândurile pe care le am cu privire la voi, zice Domnul, gânduri de pace și nu de rău, ca să vă dau un viitor și o speranță.",
|
"verse": "Căci Eu știu gândurile pe care le am cu privire la voi, zice Domnul, gânduri de pace și nu de rău, ca să vă dau un viitor și o speranță.",
|
||||||
"reference": "Ieremia 29:11",
|
"reference": "Ieremia 29:11",
|
||||||
@@ -481,5 +481,15 @@
|
|||||||
"back": "Înapoi",
|
"back": "Înapoi",
|
||||||
"next": "Următorul",
|
"next": "Următorul",
|
||||||
"previous": "Anterior"
|
"previous": "Anterior"
|
||||||
|
},
|
||||||
|
"seo": {
|
||||||
|
"title": "Ghid Biblic – Studiu Biblic Online cu AI, Versete Zilnice și Comunitate de Rugăciune",
|
||||||
|
"description": "Ghid Biblic este o aplicație de studiu biblic online cu chat AI, căutare rapidă de versete și o comunitate de rugăciune. Primește versete zilnice și răspunsuri clare din Scriptură.",
|
||||||
|
"keywords": "studiu biblic online, aplicație biblică, chat AI biblic, versete biblice zilnice, comunitate de rugăciune, citește Biblia online, căutare versete, Biblia Română",
|
||||||
|
"ogTitle": "Ghid Biblic – Studiu Biblic Online cu AI",
|
||||||
|
"ogDescription": "Citește Biblia online, pune întrebări prin chat AI și alătură-te unei comunități de rugăciune. Primește versete zilnice în inbox.",
|
||||||
|
"twitterTitle": "Ghid Biblic – Studiu Biblic Online cu AI",
|
||||||
|
"twitterDescription": "Aplicație biblică online cu chat AI, versete zilnice și comunitate de rugăciune.",
|
||||||
|
"footer": "Ghid Biblic – aplicație de studiu biblic online, cu chat AI, versete zilnice și comunitate de rugăciune."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import type { NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
// Avoid importing Node-only modules in Middleware (Edge runtime)
|
|
||||||
import createIntlMiddleware from 'next-intl/middleware'
|
import createIntlMiddleware from 'next-intl/middleware'
|
||||||
|
import { locales } from './i18n'
|
||||||
|
|
||||||
// Internationalization configuration
|
// Internationalization configuration
|
||||||
const intlMiddleware = createIntlMiddleware({
|
const intlMiddleware = createIntlMiddleware({
|
||||||
locales: ['ro', 'en'],
|
locales: [...locales],
|
||||||
defaultLocale: 'ro'
|
defaultLocale: 'ro',
|
||||||
|
localePrefix: 'always'
|
||||||
})
|
})
|
||||||
|
|
||||||
// Note: Avoid using Prisma or any Node-only APIs in Middleware.
|
// Note: Avoid using Prisma or any Node-only APIs in Middleware.
|
||||||
@@ -71,10 +72,11 @@ export const config = {
|
|||||||
// Match all pathnames except for
|
// Match all pathnames except for
|
||||||
// - api routes
|
// - api routes
|
||||||
// - _next (Next.js internals)
|
// - _next (Next.js internals)
|
||||||
|
// - _vercel
|
||||||
// - static files (images, etc.)
|
// - static files (images, etc.)
|
||||||
'/((?!api|_next|_vercel|.*\\..*).*)',
|
// - favicon.ico, robots.txt, sitemap.xml
|
||||||
// However, match all pathnames within `/api`, except for the Middleware to run there
|
'/((?!api|_next|_vercel|.*\\..*|favicon.ico|robots.txt|sitemap.xml).*)',
|
||||||
'/api/:path*',
|
// Match internationalized pathnames
|
||||||
'/dashboard/:path*'
|
'/(ro|en)/:path*'
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ const withNextIntl = require('next-intl/plugin')('./i18n.ts');
|
|||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'standalone',
|
|
||||||
typedRoutes: false,
|
typedRoutes: false,
|
||||||
|
trailingSlash: false,
|
||||||
|
poweredByHeader: false,
|
||||||
|
compress: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = withNextIntl(nextConfig)
|
module.exports = withNextIntl(nextConfig)
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 663 B |
Reference in New Issue
Block a user