diff --git a/.env.local b/.env.local index bd28e8f..9575ef8 100644 --- a/.env.local +++ b/.env.local @@ -11,4 +11,5 @@ NEXT_SOCIAL_TWITTER="https://twitter.com/user" # I dont h NEXT_SOCIAL_GITHUB_STATE="true" # I Have GitHub so this is True (if you dont then set this to false) # NEXT_SOCIAL_GITHUB_LINK_IF_TRUE="http://github.com/ZockerKatze" # If you have GitHub then paste your link here # NEXT_SOCIAL_BUYMEACOFFEE="https://coff.ee/rattatwinko" -PORT=8080 # This is unused. You can safely delete if you want. # \ No newline at end of file +PORT=8080 # This is unused. You can safely delete if you want. # +BASE_URL=/blog # This is the subpath! \ No newline at end of file diff --git a/README.md b/README.md index 965c27f..0188736 100644 --- a/README.md +++ b/README.md @@ -500,4 +500,26 @@ For issues and questions, please check the project structure and API documentati - **๐Ÿ”„ Force Reparse Button**: One-click cache clearing and post reparsing - **๐Ÿ“Š Enhanced Rust Status**: Real-time parser performance monitoring - **๐Ÿ” Improved Log Management**: Better filtering and search capabilities -- **๐Ÿ“ Directory Health Monitoring**: Comprehensive file system diagnostics \ No newline at end of file +- **๐Ÿ“ Directory Health Monitoring**: Comprehensive file system diagnostics + +## Configuring a Base URL for Proxy Hosting + +If you want to host your app behind a subpath (e.g. `http://localhost:3000/blog/`), set the base URL in `.env.local`: + +``` +BASE_URL=/blog +``` + +This will automatically prefix all internal links, API calls, and static assets with `/blog`. Make sure your reverse proxy (e.g. nginx) is configured to forward requests from `/blog` to your app. + +### Example nginx config + +``` +location /blog/ { + proxy_pass http://localhost:3000/blog/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} +``` \ No newline at end of file diff --git a/electron/main.js b/electron/main.js index 471c826..bf6b674 100644 --- a/electron/main.js +++ b/electron/main.js @@ -21,7 +21,9 @@ function createWindow() { // Load the Next.js app if (isDev) { - mainWindow.loadURL('http://localhost:3000'); + const baseUrl = process.env.BASE_URL || ''; + const url = `http://localhost:3000${baseUrl}`; + mainWindow.loadURL(url); mainWindow.webContents.openDevTools(); } else { mainWindow.loadFile(path.join(__dirname, '../.next/server/pages/index.html')); diff --git a/next.config.js b/next.config.js index d68bd34..4cf6a7f 100644 --- a/next.config.js +++ b/next.config.js @@ -7,6 +7,10 @@ const nextConfig = { experimental: { serverComponentsExternalPackages: ['chokidar'] }, + basePath: process.env.BASE_URL || '', + env: { + NEXT_PUBLIC_BASE_URL: process.env.BASE_URL || '', + }, // Handle API routes that shouldn't be statically generated async headers() { return [ diff --git a/posts/about.md b/posts/about.md index a7ac660..70228d8 100644 --- a/posts/about.md +++ b/posts/about.md @@ -6,35 +6,4 @@ author: rattatwinko summary: This is the about page --- -# About Me - -_**I am rattatwinko**_ - -I created this Project because of the lack of Blog's that use Markdown. -It really is sad that there are so many blog platforms which are shit. - -## What I used: -- TypeScript -- Next.JS -- Rust -- Monaco (for a beautiful Editing experience) -- More shit which you can check out in the Repo - -## What I do - -School. -Coding. -Not more not less. - -### Socials - - - -
- -
- -
- -
- +_**config this in the monaco editor in the admin panel**_ diff --git a/posts/welcome.md b/posts/welcome.md index cc7acd3..964acb1 100644 --- a/posts/welcome.md +++ b/posts/welcome.md @@ -535,3 +535,9 @@ If you have seen this is not very mindfull of browser resources tho. > *"DEVELOPERS! DEVELOPERS! DEVELOPERS!"* - Steve Ballmer > > โ€” Rattatwinko, 2025 Q3 + +## Hosting behind a subpath (nginx proxy) + +If you want to serve your blog at a subpath (e.g. `/blog`), set `BASE_URL=/blog` in your `.env.local` file. All internal links and API calls will use this base path automatically. + +Example: Your blog will be available at `http://localhost:3000/blog` diff --git a/src/app/AboutButton.tsx b/src/app/AboutButton.tsx index 760f1d6..857c1ec 100644 --- a/src/app/AboutButton.tsx +++ b/src/app/AboutButton.tsx @@ -1,6 +1,7 @@ 'use client'; import BadgeButton from './BadgeButton'; import { useRouter } from 'next/navigation'; +import { withBaseUrl } from '@/lib/baseUrl'; const InfoIcon = (
{blogOwner}'s Blog
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 2a1b5ac..3ebf6fa 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,6 +1,7 @@ "use client"; import React, { useEffect, useState } from "react"; +import { withBaseUrl } from '@/lib/baseUrl'; interface Post { slug: string; @@ -23,7 +24,7 @@ export default function AboutPage() { try { setLoading(true); setError(null); - const response = await fetch("/api/posts/about"); + const response = await fetch(withBaseUrl('/api/posts/about')); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } diff --git a/src/app/admin/editor/page.tsx b/src/app/admin/editor/page.tsx index 3baa12f..8ae184f 100644 --- a/src/app/admin/editor/page.tsx +++ b/src/app/admin/editor/page.tsx @@ -4,6 +4,7 @@ import dynamic from "next/dynamic"; import { useRouter } from "next/navigation"; import "@fontsource/jetbrains-mono"; import { marked } from "marked"; +import { withBaseUrl } from '@/lib/baseUrl'; const MonacoEditor = dynamic(() => import("@monaco-editor/react"), { ssr: false }); @@ -178,7 +179,7 @@ export default function EditorPage() { // Fetch file tree useEffect(() => { - fetch("/api/posts") + fetch(withBaseUrl('/api/posts')) .then(r => r.json()) .then(setTree); }, []); @@ -187,7 +188,7 @@ export default function EditorPage() { useEffect(() => { if (!selectedSlug) return; setLoading(true); - fetch(`/api/posts/${encodeURIComponent(selectedSlug)}`) + fetch(withBaseUrl(`/api/posts/${encodeURIComponent(selectedSlug)}`)) .then(r => r.json()) .then(data => { const { frontmatter, content } = extractFrontmatter(data.raw || data.content || ""); @@ -206,7 +207,7 @@ export default function EditorPage() { try { // First save the file - const saveResponse = await fetch(`/api/posts/${encodeURIComponent(selectedSlug)}`, { + const saveResponse = await fetch(withBaseUrl(`/api/posts/${encodeURIComponent(selectedSlug)}`), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ markdown: fileContent }) @@ -217,7 +218,7 @@ export default function EditorPage() { } // Then call Rust backend to reparse this specific post - const reparseResponse = await fetch(`/api/admin/posts?reparsePost=${encodeURIComponent(selectedSlug)}`); + const reparseResponse = await fetch(withBaseUrl(`/api/admin/posts?reparsePost=${encodeURIComponent(selectedSlug)}`)); if (!reparseResponse.ok) { console.warn('Failed to reparse post, but file was saved'); diff --git a/src/app/admin/manage/page.tsx b/src/app/admin/manage/page.tsx index 344d6ac..04389cb 100644 --- a/src/app/admin/manage/page.tsx +++ b/src/app/admin/manage/page.tsx @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; +import { withBaseUrl } from '@/lib/baseUrl'; interface Post { type: 'post'; @@ -26,7 +27,7 @@ type Node = Post | Folder; // Helper to get folder details async function getFolderDetails(path: string): Promise<{ created: string, items: number, size: number, error?: string }> { try { - const res = await fetch(`/api/admin/folders/details?path=${encodeURIComponent(path)}`); + const res = await fetch(withBaseUrl(`/api/admin/folders/details?path=${encodeURIComponent(path)}`)); if (!res.ok) throw new Error('API error'); return await res.json(); } catch (e) { @@ -38,7 +39,7 @@ async function getFolderDetails(path: string): Promise<{ created: string, items: // Helper to get post size and creation date async function getPostSize(slug: string): Promise<{ size: number | null, created: string | null }> { try { - const res = await fetch(`/api/admin/posts/size?slug=${encodeURIComponent(slug)}`); + const res = await fetch(withBaseUrl(`/api/admin/posts/size?slug=${encodeURIComponent(slug)}`)); if (!res.ok) throw new Error('API error'); const data = await res.json(); return { size: data.size, created: data.created }; @@ -76,7 +77,7 @@ export default function ManagePage() { const loadContent = async () => { try { - const response = await fetch('/api/posts'); + const response = await fetch(withBaseUrl('/api/posts')); const data = await response.json(); setNodes(data); } catch (error) { @@ -140,7 +141,7 @@ export default function ManagePage() { type: deleteConfirm.item.type }); - const response = await fetch('/api/admin/delete', { + const response = await fetch(withBaseUrl('/api/admin/delete'), { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -170,7 +171,7 @@ export default function ManagePage() { // Move post API call const movePost = async (post: Post, targetFolder: string[]) => { try { - const response = await fetch('/api/admin/posts/move', { + const response = await fetch(withBaseUrl('/api/admin/posts/move'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -239,12 +240,12 @@ export default function ManagePage() {

Inhaltsverwaltung

- Zum Admin-Panel - +
{/* VS Code Editor Button */} ))}
- Zur Inhaltsverwaltung + Zur Inhaltsverwaltung
)}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 691bc08..1dab2ef 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -7,6 +7,7 @@ import AboutButton from './AboutButton'; import BadgeButton from './BadgeButton'; import HeaderButtons from './HeaderButtons'; import MobileNav from './MobileNav'; +import { withBaseUrl } from '@/lib/baseUrl'; const inter = Inter({ subsets: ['latin'] }); @@ -46,11 +47,11 @@ export default function RootLayout({ return ( - - - - - + + + + + diff --git a/src/app/page.tsx b/src/app/page.tsx index 32abdf0..27221b9 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'; import Link from 'next/link'; import { format } from 'date-fns'; import React from 'react'; +import { withBaseUrl } from '@/lib/baseUrl'; interface Post { type: 'post'; @@ -47,7 +48,7 @@ export default function Home() { const setupSSE = () => { try { - eventSource = new EventSource('/api/posts/stream'); + eventSource = new EventSource(withBaseUrl('/api/posts/stream')); eventSource.onmessage = (event) => { try { @@ -101,7 +102,7 @@ export default function Home() { try { setIsLoading(true); setError(null); - const response = await fetch('/api/posts'); + const response = await fetch(withBaseUrl('/api/posts')); if (!response.ok) { throw new Error(`API error: ${response.status}`); } diff --git a/src/app/posts/[...slug]/page.tsx b/src/app/posts/[...slug]/page.tsx index d78ceae..dc60754 100644 --- a/src/app/posts/[...slug]/page.tsx +++ b/src/app/posts/[...slug]/page.tsx @@ -4,6 +4,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { format } from 'date-fns'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import { withBaseUrl } from '@/lib/baseUrl'; interface Post { slug: string; @@ -55,7 +56,7 @@ export default function PostPage({ params }: { params: { slug: string[] } }) { const setupSSE = () => { try { - eventSource = new EventSource('/api/posts/stream'); + eventSource = new EventSource(withBaseUrl('/api/posts/stream')); eventSource.onmessage = (event) => { try { @@ -109,7 +110,7 @@ export default function PostPage({ params }: { params: { slug: string[] } }) { try { setLoading(true); setError(null); - const response = await fetch(`/api/posts/${encodeURIComponent(slugPath)}`); + const response = await fetch(withBaseUrl(`/api/posts/${encodeURIComponent(slugPath)}`)); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } diff --git a/src/lib/baseUrl.ts b/src/lib/baseUrl.ts new file mode 100644 index 0000000..b2f7f17 --- /dev/null +++ b/src/lib/baseUrl.ts @@ -0,0 +1,16 @@ +declare const process: { env: { NEXT_PUBLIC_BASE_URL?: string } }; + +export function withBaseUrl(path: string): string { + let base = ''; + if (typeof process !== 'undefined' && process.env && process.env.NEXT_PUBLIC_BASE_URL) { + base = process.env.NEXT_PUBLIC_BASE_URL; + } + if (!base || base === '/') return path; + // Ensure base starts with / and does not end with / + if (!base.startsWith('/')) base = '/' + base; + if (base.endsWith('/')) base = base.slice(0, -1); + // Ensure path starts with / + if (!path.startsWith('/')) path = '/' + path; + // Avoid double slashes + return base + path; +} \ No newline at end of file