Full stack development feasibility test: building a modern personal blog system

Full stack development feasibility test: building a modern personal blog system

actopas
·Posted 7 months ago·
5 Viewed

Core Technology Stack

  • Next.js 14 + React 18: Leveraging Next.js's powerful SSR capabilities combined with React 18's new features to build a high-performance frontend application.
import { unstable_setRequestLocale } from 'next-intl/server';

import { ScrollIndicator } from '@/components/scroll-indicator';

import { BlogList, getPinnedBlogs } from '@/features/blog';
import { HeroSection } from '@/features/home';
import { ProjectTimeline, getPinnedProjects } from '@/features/project';

export default async function Page({
  params: { locale },
}: {
  params: { locale: string };
}) {
  unstable_setRequestLocale(locale);

  const [projectsData, blogsData] = await Promise.all([
    getPinnedProjects(locale),
    getPinnedBlogs(locale),
  ]);
  const { projects } = projectsData;
  const { blogs, uvMap } = blogsData;
  return (
    <div>
      <div className="h-[calc(100vh-64px)] grid place-content-center relative">
        <HeroSection />
      </div>
      <div className="grid place-content-center absolute bottom-8 md:bottom-12 inset-x-0">
        <ScrollIndicator />
      </div>
      <ProjectTimeline projects={projects} />
      <BlogList blogs={blogs} uvMap={uvMap} />
    </div>
  );
}
  • TypeScript: The entire project is developed using TypeScript, providing type safety and a better development experience.

  • Prisma: Simplifies database operations, making CRUD operations more intuitive.

export const getPublishedBlogs = async (locale: string) => {
  const blogs = await prisma.blog.findMany({
    where: {
      published: true,
    },
    include: {
      tags: true,
    },
    orderBy: {
      createdAt: 'desc',
    },
  });

  const localizedBlogs = blogs.map((blog) => ({
    ...blog,
    title: locale === 'zh' ? blog.titleZH : blog.titleEN,
    description: locale === 'zh' ? blog.descriptionZH : blog.descriptionEN,
    body: locale === 'zh' ? blog.bodyZH : blog.bodyEN,
  }));

  // ... other code
};
  • Redis + ioredis: Used for tracking article PV/UV data.

  • Tailwind CSS + shadcn/ui: Quickly build beautiful UI interfaces.

  plugins: [
    require('tailwindcss-animate'),
    require('@tailwindcss/typography'),
    // Load the plugin that displays the screen size in development mode
    process.env.NODE_ENV === 'development' &&
      require('tailwindcss-debug-screens'),
    // Iconify plugin
    addDynamicIconSelectors(),
    // Animation plugin
    require('tailwindcss-animated'),
    require('tailwindcss-textshadow'),
  ],
} satisfies Config;

Form Handling and Validation

The project uses a combination of shadcn/form, react-hook-form, and zod for form validation, greatly simplifying form handling logic.

export const CreateBlogForm = () => {
  // ... other code

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
        {/* ... other form fields */}
        <FormField
          control={form.control}
          name="tags"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Tag</FormLabel>
              <FormControl>
                <div className="grid grid-cols-12 gap-4 items-center">
                  <div className="col-span-10">
                    <Combobox
                      options={
                        tags?.map((el) => ({
                          label: el.name,
                          value: el.id,
                        })) ?? []
                      }
                      multiple
                      clearable
                      selectPlaceholder="Please choose Tags"
                      value={field.value}
                      onValueChange={field.onChange}
                    />
                  </div>
                  <CreateTagButton refreshAsync={getTagsQuery.refreshAsync} />
                </div>
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        {/* ... other form fields */}
      </form>
    </Form>
  );
};

Markdown Support

Bytemd is used to implement Markdown writing and preview functionality, providing convenience for content creation.

Theme Switching

Integrated next-theme to support light and dark theme switching, enhancing user experience.

SEO Optimization

Using next-sitemap to automatically generate a site-wide sitemap, beneficial for search engine indexing. Additionally, we utilize dynamic metadata generation to optimize SEO for each page.

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

  if (isNil(blog)) {
    return {};
  }

  const title = params.locale === 'zh' ? blog.titleZH : blog.titleEN;
  const description =
    params.locale === 'zh' ? blog.descriptionZH : blog.descriptionEN;

  return {
    title: `${title} - ${WEBSITE}`,
    description: description,
    keywords: blog.tags.map((el) => el.name).join(','),
  };
}

Authentication

Adopting the latest next-auth v5 and siwe to support GitHub account and Ethereum wallet login for the backend, ensuring system security.

Image Processing

After uploading images, sharp is used to compress and convert them to webp format, then uploaded to Alibaba Cloud OSS, optimizing image loading speed.

  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: '**.aliyuncs.com',
      },
      // ... other configuration
    ],
  }

Responsive Design

Adapted for different screen sizes to ensure good display across various devices.

export const BlogDetailPage = ({ blog, uv = 0 }: BlogDetailProps) => {
  // ... other code

  return (
    <div className="md:max-w-screen-md 2xl:max-w-6xl md:px-0 md:mx-auto pt-12 md:pt-24 grid gap-9 px-6">
      <article className="max-w-[678px] mx-auto overflow-hidden w-full break-all">
        {blog.cover && (
          <img
            src={blog.cover}
            alt={title}
            className="max-w-screen-md 2xl:max-w-6xl h-auto mb-16 w-full"
          />
        )}
        <h1 className="mb-4 text-2xl md:text-4xl font-extrabold ">{title}</h1>
         {/* ... other content */}
      </article>
       {/* ... other components */}
    </div>
  );
};

Backend Management

Integrated complete backend management functionality, including CRUD operations for blog posts, tags, etc.

export const AdminBlogListPage = ({ session }: WithSession) => {
  // ... other code

  return (
    <AdminContentLayout
      pageHeader={/* ... */}
    >
      {/* ... other components */}
      <DataTable
        columns={columns}
        data={data}
        total={getBlogsQuery.data?.total}
        loading={getBlogsQuery.loading}
        params={{ ...params }}
        updateParams={updateParams}
        noResult={
          <div className="grid place-content-center gap-4 py-16">
            <IllustrationNoContent />
            <p>Empty</p>
            <Button onClick={handleGoToCreate}>Create</Button>
          </div>
        }
      />
    </AdminContentLayout>
  );
};

Internationalization Support

To make our blog system serve a wider audience, we integrated the next-intl module to implement seamless internationalization functionality.

Application of next-intl

  1. Route-level Language Switching:
    We utilize Next.js's dynamic routing feature to include the language code as part of the URL.

  2. Component-level Text Translation:
    In components, we use the useTranslation hook to retrieve and render translated text.

import { useLocale, useTranslations } from 'next-intl';

export const BlogDetailPage = ({ blog, uv = 0 }: BlogDetailProps) => {
  const locale = useLocale();
  const t = useTranslations('BlogDetail');

  // ... 

  return (
    // ...
    <span>
      {t('posted', { time: toFromNow(blog.createdAt, locale) })}
    </span>
    // ...
  );
}
  1. Localization of Dynamic Content:
    For multilingual content stored in the database, we dynamically select the correct language version based on the current language environment.

  2. SEO Optimization:
    We use next-intl to generate localized metadata, enhancing multilingual SEO effectiveness.

Configuration and Integration

To seamlessly integrate next-intl, we made corresponding configurations in next.config.mjs:

import createNextIntlPlugin from 'next-intl/plugin';

const withNextIntl = createNextIntlPlugin();

// ... other configurations

// Use withNextIntl ​​to wrap configuration
export default withNextIntl(withBundleAnalyzer(config));

Conclusion

Although this is just a personal blog system, it contains many features required by enterprise-level applications. It covers all aspects of full-stack development, from front-end to back-end, from database to cache, from UI design to performance optimization, and internationalization support. Through this project, I not only updated part of the technology stack, but also gained a deeper understanding and verification of more modern web application architecture and development processes.
In the future, I will continue to optimize this project, add more features, and apply the lessons learned to larger projects. At the same time, I will continue to explore more internationalization best practices to ensure that my application can truly serve global users.
For other friends who are learning full-stack development, I suggest that you can also try similar projects. Building a complete application from scratch will give you a more comprehensive understanding of full-stack development. Remember, practice is the best way to learn! At the same time, consider adding internationalization support to your project, which will not only expand the audience of the application, but also provide a more inclusive user experience.

Front-end
Back-end
SEO
$ cd ..