diff --git a/content/author/fox.mdx b/content/author/fox.mdx
deleted file mode 100644
index 6151ce7..0000000
--- a/content/author/fox.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: fox
-name: Fox
-avatar: /images/avatars/fox.png
----
diff --git a/content/author/fox.zh.mdx b/content/author/fox.zh.mdx
deleted file mode 100644
index 6151ce7..0000000
--- a/content/author/fox.zh.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: fox
-name: Fox
-avatar: /images/avatars/fox.png
----
diff --git a/content/author/mkdirs.mdx b/content/author/mkdirs.mdx
deleted file mode 100644
index e5cf61f..0000000
--- a/content/author/mkdirs.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: mkdirs
-name: Mkdirs
-avatar: /images/avatars/mkdirs.png
----
diff --git a/content/author/mkdirs.zh.mdx b/content/author/mkdirs.zh.mdx
deleted file mode 100644
index 61ba558..0000000
--- a/content/author/mkdirs.zh.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: mkdirs
-name: Mkdirs模板
-avatar: /images/avatars/mkdirs.png
----
diff --git a/content/author/mksaas.mdx b/content/author/mksaas.mdx
deleted file mode 100644
index 77581c6..0000000
--- a/content/author/mksaas.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: mksaas
-name: MkSaaS
-avatar: /images/avatars/mksaas.png
----
diff --git a/content/author/mksaas.zh.mdx b/content/author/mksaas.zh.mdx
deleted file mode 100644
index 294ec63..0000000
--- a/content/author/mksaas.zh.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: mksaas
-name: MkSaaS模板
-avatar: /images/avatars/mksaas.png
----
diff --git a/content/category/company.mdx b/content/category/company.mdx
deleted file mode 100644
index c3ac928..0000000
--- a/content/category/company.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: company
-name: Company
-description: Company news and updates
----
diff --git a/content/category/company.zh.mdx b/content/category/company.zh.mdx
deleted file mode 100644
index 5217933..0000000
--- a/content/category/company.zh.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: company
-name: 公司
-description: 公司新闻和更新
----
diff --git a/content/category/news.mdx b/content/category/news.mdx
deleted file mode 100644
index 550a4cb..0000000
--- a/content/category/news.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: news
-name: News
-description: News and updates about MkSaaS
----
diff --git a/content/category/news.zh.mdx b/content/category/news.zh.mdx
deleted file mode 100644
index ffc2670..0000000
--- a/content/category/news.zh.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: news
-name: 新闻
-description: 最新新闻和更新
----
diff --git a/content/category/product.mdx b/content/category/product.mdx
deleted file mode 100644
index aa07989..0000000
--- a/content/category/product.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: product
-name: Product
-description: Products and services powered by MkSaaS
----
diff --git a/content/category/product.zh.mdx b/content/category/product.zh.mdx
deleted file mode 100644
index 3027a7d..0000000
--- a/content/category/product.zh.mdx
+++ /dev/null
@@ -1,5 +0,0 @@
----
-slug: product
-name: 产品
-description: 产品和服务
----
diff --git a/src/app/[locale]/(marketing)/blog/(blog)/category/[slug]/page.tsx b/src/app/[locale]/(marketing)/blog/(blog)/category/[slug]/page.tsx
deleted file mode 100644
index b8d0a41..0000000
--- a/src/app/[locale]/(marketing)/blog/(blog)/category/[slug]/page.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import BlogGridWithPagination from '@/components/blog/blog-grid-with-pagination';
-import { LOCALES } from '@/i18n/routing';
-import { getPaginatedBlogPosts } from '@/lib/blog/data';
-import { constructMetadata } from '@/lib/metadata';
-import { getUrlWithLocale } from '@/lib/urls/urls';
-import { allCategories } from 'content-collections';
-import type { Locale } from 'next-intl';
-import { getTranslations } from 'next-intl/server';
-import { notFound } from 'next/navigation';
-
-// Generate all static params for SSG (locale + category)
-export function generateStaticParams() {
- const params: { locale: string; slug: string }[] = [];
- for (const locale of LOCALES) {
- const localeCategories = allCategories.filter(
- (category) => category.locale === locale
- );
- for (const category of localeCategories) {
- params.push({ locale, slug: category.slug });
- }
- }
- return params;
-}
-
-// Generate metadata for each static category page (locale + category)
-export async function generateMetadata({ params }: BlogCategoryPageProps) {
- const { locale, slug } = await params;
- const category = allCategories.find(
- (category) => category.locale === locale && category.slug === slug
- );
- if (!category) {
- notFound();
- }
- const t = await getTranslations({ locale, namespace: 'Metadata' });
- const canonicalPath = `/blog/category/${slug}`;
- return constructMetadata({
- title: `${category.name} | ${t('title')}`,
- description: category.description,
- canonicalUrl: getUrlWithLocale(canonicalPath, locale),
- });
-}
-
-interface BlogCategoryPageProps {
- params: Promise<{
- locale: Locale;
- slug: string;
- }>;
-}
-
-export default async function BlogCategoryPage({
- params,
-}: BlogCategoryPageProps) {
- const { locale, slug } = await params;
- const category = allCategories.find(
- (category) => category.locale === locale && category.slug === slug
- );
- if (!category) {
- notFound();
- }
- const currentPage = 1;
- const { paginatedPosts, totalPages } = getPaginatedBlogPosts({
- locale,
- page: currentPage,
- category: slug,
- });
- return (
-
- );
-}
diff --git a/src/app/[locale]/(marketing)/blog/(blog)/category/[slug]/page/[page]/page.tsx b/src/app/[locale]/(marketing)/blog/(blog)/category/[slug]/page/[page]/page.tsx
deleted file mode 100644
index 7c09807..0000000
--- a/src/app/[locale]/(marketing)/blog/(blog)/category/[slug]/page/[page]/page.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import BlogGridWithPagination from '@/components/blog/blog-grid-with-pagination';
-import { websiteConfig } from '@/config/website';
-import { LOCALES } from '@/i18n/routing';
-import { getPaginatedBlogPosts } from '@/lib/blog/data';
-import { constructMetadata } from '@/lib/metadata';
-import { getUrlWithLocale } from '@/lib/urls/urls';
-import { allCategories, allPosts } from 'content-collections';
-import type { Locale } from 'next-intl';
-import { getTranslations } from 'next-intl/server';
-import { notFound } from 'next/navigation';
-
-// Generate all static params for SSG (locale + category + pagination)
-export function generateStaticParams() {
- const params: { locale: string; slug: string; page: string }[] = [];
- for (const locale of LOCALES) {
- const localeCategories = allCategories.filter(
- (category) => category.locale === locale
- );
- for (const category of localeCategories) {
- const totalPages = Math.ceil(
- allPosts.filter(
- (post) =>
- post.locale === locale &&
- post.categories.some((cat) => cat && cat.slug === category.slug)
- ).length / websiteConfig.blog.paginationSize
- );
- for (let page = 2; page <= totalPages; page++) {
- params.push({ locale, slug: category.slug, page: String(page) });
- }
- }
- }
- return params;
-}
-
-// Generate metadata for each static category page (locale + category + pagination)
-export async function generateMetadata({ params }: BlogCategoryPageProps) {
- const { locale, slug, page } = await params;
- const category = allCategories.find(
- (category) => category.slug === slug && category.locale === locale
- );
- if (!category) {
- notFound();
- }
- const t = await getTranslations({ locale, namespace: 'Metadata' });
- const canonicalPath = `/blog/category/${slug}/page/${page}`;
- return constructMetadata({
- title: `${category.name} | ${t('title')}`,
- description: category.description,
- canonicalUrl: getUrlWithLocale(canonicalPath, locale),
- });
-}
-
-interface BlogCategoryPageProps {
- params: Promise<{
- locale: Locale;
- slug: string;
- page: string;
- }>;
-}
-
-export default async function BlogCategoryPage({
- params,
-}: BlogCategoryPageProps) {
- const { locale, slug, page } = await params;
- const currentPage = Number(page);
- const category = allCategories.find(
- (category) => category.slug === slug && category.locale === locale
- );
- if (!category) {
- notFound();
- }
- const { paginatedPosts, totalPages } = getPaginatedBlogPosts({
- locale,
- page: currentPage,
- category: slug,
- });
- return (
-
- );
-}
diff --git a/src/app/[locale]/(marketing)/blog/(blog)/layout.tsx b/src/app/[locale]/(marketing)/blog/(blog)/layout.tsx
deleted file mode 100644
index 1302a1a..0000000
--- a/src/app/[locale]/(marketing)/blog/(blog)/layout.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { BlogCategoryFilter } from '@/components/blog/blog-category-filter';
-import Container from '@/components/layout/container';
-import type { NextPageProps } from '@/types/next-page-props';
-import { allCategories } from 'content-collections';
-import { getTranslations } from 'next-intl/server';
-import type { PropsWithChildren } from 'react';
-
-interface BlogListLayoutProps extends PropsWithChildren, NextPageProps {}
-
-export default async function BlogListLayout({
- children,
- params,
-}: BlogListLayoutProps) {
- const resolvedParams = await params;
- const { locale } = resolvedParams;
- const t = await getTranslations('BlogPage');
-
- // Filter categories by locale
- const categoryList = allCategories.filter(
- (category) => category.locale === locale
- );
-
- return (
-
-
- {/* Header */}
-
-
- {t('title')}
-
-
- {t('subtitle')}
-
-
-
-
-
-
-
{children}
-
- );
-}
diff --git a/src/app/[locale]/(marketing)/blog/(blog)/loading.tsx b/src/app/[locale]/(marketing)/blog/(blog)/loading.tsx
deleted file mode 100644
index bc312b4..0000000
--- a/src/app/[locale]/(marketing)/blog/(blog)/loading.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { BlogGridSkeleton } from '@/components/blog/blog-grid';
-
-export default function Loading() {
- return ;
-}
diff --git a/src/app/[locale]/(marketing)/blog/(blog)/page.tsx b/src/app/[locale]/(marketing)/blog/(blog)/page.tsx
deleted file mode 100644
index aaf6394..0000000
--- a/src/app/[locale]/(marketing)/blog/(blog)/page.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import BlogGridWithPagination from '@/components/blog/blog-grid-with-pagination';
-import { LOCALES } from '@/i18n/routing';
-import { getPaginatedBlogPosts } from '@/lib/blog/data';
-import { constructMetadata } from '@/lib/metadata';
-import { getUrlWithLocale } from '@/lib/urls/urls';
-import type { Locale } from 'next-intl';
-import { getTranslations } from 'next-intl/server';
-
-export function generateStaticParams() {
- return LOCALES.map((locale) => ({ locale }));
-}
-
-export async function generateMetadata({ params }: BlogPageProps) {
- const { locale } = await params;
- const t = await getTranslations({ locale, namespace: 'Metadata' });
- const pt = await getTranslations({ locale, namespace: 'BlogPage' });
- const canonicalPath = '/blog';
- return constructMetadata({
- title: `${pt('title')} | ${t('title')}`,
- description: pt('description'),
- canonicalUrl: getUrlWithLocale(canonicalPath, locale),
- });
-}
-
-interface BlogPageProps {
- params: Promise<{
- locale: Locale;
- }>;
-}
-
-export default async function BlogPage({ params }: BlogPageProps) {
- const { locale } = await params;
- const currentPage = 1;
- const { paginatedPosts, totalPages } = getPaginatedBlogPosts({
- locale,
- page: currentPage,
- });
- return (
-
- );
-}
diff --git a/src/app/[locale]/(marketing)/blog/(blog)/page/[page]/page.tsx b/src/app/[locale]/(marketing)/blog/(blog)/page/[page]/page.tsx
deleted file mode 100644
index b0c42b1..0000000
--- a/src/app/[locale]/(marketing)/blog/(blog)/page/[page]/page.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import BlogGridWithPagination from '@/components/blog/blog-grid-with-pagination';
-import { websiteConfig } from '@/config/website';
-import { LOCALES } from '@/i18n/routing';
-import { getPaginatedBlogPosts } from '@/lib/blog/data';
-import { constructMetadata } from '@/lib/metadata';
-import { getUrlWithLocale } from '@/lib/urls/urls';
-import { allPosts } from 'content-collections';
-import type { Locale } from 'next-intl';
-import { getTranslations } from 'next-intl/server';
-
-export function generateStaticParams() {
- const paginationSize = websiteConfig.blog.paginationSize;
- const params: { locale: string; page: string }[] = [];
- for (const locale of LOCALES) {
- const publishedPosts = allPosts.filter(
- (post) => post.published && post.locale === locale
- );
- const totalPages = Math.max(
- 1,
- Math.ceil(publishedPosts.length / paginationSize)
- );
- for (let pageNumber = 2; pageNumber <= totalPages; pageNumber++) {
- params.push({
- locale,
- page: String(pageNumber),
- });
- }
- }
- return params;
-}
-
-export async function generateMetadata({ params }: BlogListPageProps) {
- const { locale, page } = await params;
- const t = await getTranslations({ locale, namespace: 'Metadata' });
- const pt = await getTranslations({ locale, namespace: 'BlogPage' });
- const canonicalPath = `/blog/page/${page}`;
- return constructMetadata({
- title: `${pt('title')} | ${t('title')}`,
- description: pt('description'),
- canonicalUrl: getUrlWithLocale(canonicalPath, locale),
- });
-}
-
-interface BlogListPageProps {
- params: Promise<{
- locale: Locale;
- page: string;
- }>;
-}
-
-export default async function BlogListPage({ params }: BlogListPageProps) {
- const { page, locale } = await params;
- const currentPage = Number(page);
- const { paginatedPosts, totalPages } = getPaginatedBlogPosts({
- locale,
- page: currentPage,
- });
- return (
-
- );
-}
diff --git a/src/app/[locale]/(marketing)/blog/[...slug]/layout.tsx b/src/app/[locale]/(marketing)/blog/[...slug]/layout.tsx
deleted file mode 100644
index ee15fe6..0000000
--- a/src/app/[locale]/(marketing)/blog/[...slug]/layout.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import Container from '@/components/layout/container';
-import type { PropsWithChildren } from 'react';
-
-export default function BlogPostLayout({ children }: PropsWithChildren) {
- return (
-
- {children}
-
- );
-}
diff --git a/src/app/[locale]/(marketing)/blog/[...slug]/page.tsx b/src/app/[locale]/(marketing)/blog/[...slug]/page.tsx
deleted file mode 100644
index 83233c0..0000000
--- a/src/app/[locale]/(marketing)/blog/[...slug]/page.tsx
+++ /dev/null
@@ -1,256 +0,0 @@
-import AllPostsButton from '@/components/blog/all-posts-button';
-import BlogGrid from '@/components/blog/blog-grid';
-import { BlogToc } from '@/components/blog/blog-toc';
-import { NewsletterCard } from '@/components/newsletter/newsletter-card';
-import { CustomMDXContent } from '@/components/shared/custom-mdx-content';
-import { websiteConfig } from '@/config/website';
-import { LocaleLink } from '@/i18n/navigation';
-import { LOCALES } from '@/i18n/routing';
-import { getTableOfContents } from '@/lib/blog/toc';
-import { formatDate } from '@/lib/formatter';
-import { constructMetadata } from '@/lib/metadata';
-import { getUrlWithLocale } from '@/lib/urls/urls';
-import { type Post, allPosts } from 'content-collections';
-import { CalendarIcon, ClockIcon, FileTextIcon } from 'lucide-react';
-import type { Metadata } from 'next';
-import type { Locale } from 'next-intl';
-import { getTranslations } from 'next-intl/server';
-import Image from 'next/image';
-import { notFound } from 'next/navigation';
-
-import '@/styles/mdx.css';
-
-/**
- * Gets the blog post from the params
- * @param slug - The slug of the blog post
- * @param locale - The locale of the blog post
- * @returns The blog post
- *
- * How it works:
- * /[locale]/blog/first-post:
- * params.slug = ["first-post"]
- * slug becomes "first-post" after join('/')
- * Matches post where slugAsParams === "first-post" AND locale === params.locale
- */
-async function getBlogPostFromParams(locale: Locale, slug: string) {
- // console.log('getBlogPostFromParams', locale, slug);
- // Find post with matching slug and locale
- const post = allPosts.find(
- (post) =>
- (post.slugAsParams === slug ||
- (!slug && post.slugAsParams === 'index')) &&
- post.locale === locale
- );
-
- if (!post) {
- // If no post found with the current locale, try to find one with the default locale
- const defaultPost = allPosts.find(
- (post) =>
- post.slugAsParams === slug || (!slug && post.slugAsParams === 'index')
- );
-
- return defaultPost;
- }
-
- return post;
-}
-
-/**
- * get related posts, random pick from all posts with same locale, different slug,
- * max size is websiteConfig.blog.relatedPostsSize
- */
-async function getRelatedPosts(post: Post) {
- const relatedPosts = allPosts
- .filter((p) => p.locale === post.locale)
- .filter((p) => p.slugAsParams !== post.slugAsParams)
- .sort(() => Math.random() - 0.5)
- .slice(0, websiteConfig.blog.relatedPostsSize);
-
- return relatedPosts;
-}
-
-export function generateStaticParams() {
- return LOCALES.flatMap((locale) => {
- const posts = allPosts.filter((post) => post.locale === locale);
- return posts.map((post) => ({
- locale,
- slug: [post.slugAsParams],
- }));
- });
-}
-
-export async function generateMetadata({
- params,
-}: BlogPostPageProps): Promise {
- const { locale, slug } = await params;
- const post = await getBlogPostFromParams(locale, slug.join('/'));
- if (!post) {
- notFound();
- }
-
- const t = await getTranslations({ locale, namespace: 'Metadata' });
-
- return constructMetadata({
- title: `${post.title} | ${t('title')}`,
- description: post.description,
- canonicalUrl: getUrlWithLocale(post.slug, locale),
- image: post.image,
- });
-}
-
-interface BlogPostPageProps {
- params: Promise<{
- locale: Locale;
- slug: string[];
- }>;
-}
-
-export default async function BlogPostPage(props: BlogPostPageProps) {
- const { locale, slug } = await props.params;
- const post = await getBlogPostFromParams(locale, slug.join('/'));
- if (!post) {
- notFound();
- }
-
- const publishDate = post.date;
- const date = formatDate(new Date(publishDate));
- const toc = await getTableOfContents(post.content);
-
- // getTranslations may cause error DYNAMIC_SERVER_USAGE, so we set dynamic to force-static
- const t = await getTranslations('BlogPage');
-
- // get related posts
- const relatedPosts = await getRelatedPosts(post);
-
- return (
-
- {/* content section */}
-
- {/* left column (blog post content) */}
-
- {/* Basic information */}
-
- {/* blog post image */}
-
- {post.image && (
-
- )}
-
-
- {/* blog post date and reading time */}
-
-
-
-
- {date}
-
-
-
-
-
- {t('readTime', { minutes: post.estimatedTime })}
-
-
-
-
- {/* blog post title */}
-
{post.title}
-
- {/* blog post description */}
-
{post.description}
-
-
- {/* blog post content */}
- {/* in order to make the mdx.css work, we need to add the className prose to the div */}
- {/* https://github.com/tailwindlabs/tailwindcss-typography */}
-
-
-
-
-
-
-
- {/* right column (sidebar) */}
-
-
- {/* author info */}
-
-
{t('author')}
-
-
- {post.author?.avatar && (
-
- )}
-
-
{post.author?.name}
-
-
-
- {/* categories */}
-
-
{t('categories')}
-
- {post.categories?.filter(Boolean).map(
- (category) =>
- category && (
- -
-
- {category.name}
-
-
- )
- )}
-
-
-
- {/* table of contents */}
-
-
- {t('tableOfContents')}
-
-
-
-
-
-
-
-
-
- {/* Footer section shows related posts */}
- {relatedPosts && relatedPosts.length > 0 && (
-
-
-
-
- {t('morePosts')}
-
-
-
-
-
- )}
-
- {/* newsletter */}
-
-
-
-
- );
-}