Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Make sure it uses up to date node js version
FROM node:23-alpine AS base

FROM base AS deps
RUN apk add --no-cache libc6-compat
# If you still run into build issue, go to "Problem #3: Making /app is read only.
# in case you have permission issues.
WORKDIR /app

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
RUN \
if [ -f pnpm-lock.yaml ]; then corepack enable && corepack prepare pnpm@latest --activate && pnpm i --frozen-lockfile --ignore-scripts; \
elif [ -f yarn.lock ]; then corepack enable && corepack prepare yarn@stable --activate && yarn install --frozen-lockfile --ignore-scripts; \
elif [ -f package-lock.json ]; then npm ci --ignore-scripts; \
else echo "Lockfile not found." && exit 1; \
fi

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_PRIVATE_STANDALONE=true

RUN \
if [ -f pnpm-lock.yaml ]; then corepack enable && corepack prepare pnpm@latest --activate && pnpm rebuild -r && pnpm run build; \
elif [ -f yarn.lock ]; then corepack enable && corepack prepare yarn@stable --activate && yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
else echo "Lockfile not found." && exit 1; \
fi

FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_PRIVATE_STANDALONE=true
ENV NEXT_CACHE_DIR=/tmp/next/cache

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# Prepare a writable cache directory for Next.js at runtime
RUN mkdir -p /tmp/next/cache && chown -R nextjs:nodejs /tmp/next && chmod -R 775 /tmp/next

USER nextjs

EXPOSE 3000

ENV PORT=3000

ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ A modern, responsive blog built with Next.js 15, Fumadocs MDX, and Tailwind CSS.
## 🚀 Getting Started

```bash
# Clone the repository
git clone <repo-url>
cd blog-template
# option1: build docker image and run a container
docker build -t ze-portfolio .
docker run --rm -p 3000:3000 ze-portfolio

# Install dependencies
# option2: Run locally
pnpm install

# Start development server
pnpm dev

# Build for production
Expand Down
216 changes: 108 additions & 108 deletions app/blog/[slug]/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,108 +1,108 @@
import { Metadata } from "next";
import { docs, meta } from "@/.source";
import { loader } from "fumadocs-core/source";
import { createMDXSource } from "fumadocs-mdx";
import { siteConfig } from "@/lib/site";
const blogSource = loader({
baseUrl: "/blog",
source: createMDXSource(docs, meta),
});
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
try {
const { slug } = await params;
if (!slug || slug.length === 0) {
return {
title: "Blog Not Found",
description: "The requested blog post could not be found.",
};
}
const page = blogSource.getPage([slug]);
if (!page) {
return {
title: "Blog Not Found",
description: "The requested blog post could not be found.",
};
}
const ogUrl = `${siteConfig.url}/blog/${slug}`;
const ogImage = `${ogUrl}/opengraph-image`;
return {
title: page.data.title,
description: page.data.description,
keywords: [
page.data.title,
...(page.data.tags || []),
"Blog",
"Article",
"Web Development",
"Programming",
"Technology",
"Software Engineering",
],
authors: [
{
name: page.data.author || "Magic UI",
url: siteConfig.url,
},
],
creator: page.data.author || "Magic UI",
publisher: "Magic UI",
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-video-preview": -1,
"max-image-preview": "large",
"max-snippet": -1,
},
},
openGraph: {
title: page.data.title,
description: page.data.description,
type: "article",
url: ogUrl,
publishedTime: page.data.date,
authors: [page.data.author || "Magic UI"],
tags: page.data.tags,
images: [
{
url: page.data.thumbnail || ogImage,
width: 1200,
height: 630,
alt: page.data.title,
},
],
siteName: siteConfig.name,
},
twitter: {
card: "summary_large_image",
title: page.data.title,
description: page.data.description,
images: [page.data.thumbnail || ogImage],
creator: "@dillionverma",
site: "@dillionverma",
},
alternates: {
canonical: ogUrl,
},
};
} catch (error) {
console.error("Error generating metadata:", error);
return {
title: "Blog Not Found",
description: "The requested blog post could not be found.",
};
}
}
import { Metadata } from "next";
import { docs, meta } from "@/.source";
import { loader } from "fumadocs-core/source";
import { createMDXSource } from "fumadocs-mdx";
import { siteConfig } from "@/lib/site";

const blogSource = loader({
baseUrl: "/blog",
source: createMDXSource(docs, meta),
});

export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
try {
const { slug } = await params;

if (!slug || slug.length === 0) {
return {
title: "Blog Not Found",
description: "The requested blog post could not be found.",
};
}

const page = blogSource.getPage([slug]);

if (!page) {
return {
title: "Blog Not Found",
description: "The requested blog post could not be found.",
};
}

const ogUrl = `${siteConfig.url}/blog/${slug}`;
const ogImage = `${ogUrl}/opengraph-image`;

return {
title: page.data.title,
description: page.data.description,
keywords: [
page.data.title,
...(page.data.tags || []),
"Blog",
"Article",
"Web Development",
"Programming",
"Technology",
"Software Engineering",
],
authors: [
{
name: page.data.author || "Magic UI",
url: siteConfig.url,
},
],
creator: page.data.author || "Magic UI",
publisher: "Magic UI",
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-video-preview": -1,
"max-image-preview": "large",
"max-snippet": -1,
},
},
openGraph: {
title: page.data.title,
description: page.data.description,
type: "article",
url: ogUrl,
publishedTime: page.data.date,
authors: [page.data.author || "Magic UI"],
tags: page.data.tags,
images: [
{
url: page.data.thumbnail || ogImage,
width: 1200,
height: 630,
alt: page.data.title,
},
],
siteName: siteConfig.name,
},
twitter: {
card: "summary_large_image",
title: page.data.title,
description: page.data.description,
images: [page.data.thumbnail || ogImage],
creator: "@dillionverma",
site: "@dillionverma",
},
alternates: {
canonical: ogUrl,
},
};
} catch (error) {
console.error("Error generating metadata:", error);
return {
title: "Blog Not Found",
description: "The requested blog post could not be found.",
};
}
}
4 changes: 2 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ export default async function HomePage({
<div className="max-w-7xl mx-auto w-full">
<div className="flex flex-col gap-2">
<h1 className="font-medium text-4xl md:text-5xl tracking-tighter">
Magic UI Blog
Portfolio
</h1>
<p className="text-muted-foreground text-sm md:text-base lg:text-lg">
Latest news and updates from Magic UI.
All my projects and work.
</p>
</div>
</div>
Expand Down
Loading