使用Next.js构建现代化网站模板的技术指南

本文详细介绍了使用Next.js框架构建网站模板的完整技术流程,包括技术栈选择、页面结构设计、组件开发、动态路由实现和部署方案,为开发者提供实用的前端开发指导。

如何构建网站模板

欢迎阅读新博客!今天我们将讨论如何构建网站模板。

故事从同时处理多个项目开始,这是大多数开发者的常态。我从未见过只专注于一个项目的开发者。只要大部分工作没有白费,这并非浪费时间的行为。从轻松的角度看,每日的努力终有回报——这是我在竞赛考试中排名不佳时叔叔告诉我的。

处理多个项目并非浪费时间,反而能帮助你有效学习时间管理。快速将产品推向市场是个好方法。要将任何产品推向市场,就需要前端网站——这正是许多有C语言背景的软件开发者不愿做的工作,这也是本篇博客的由来。

2025年构建前端网站的技术栈

  • React或Nextjs作为前端框架
  • JSX或React作为开发语言(HTML、CSS和JS不再是开发者的首选)
  • Tailwind CSS用于样式设计和响应式布局
  • 图标主要使用react-icons和lucide-react
  • 部署到Vercel进行免费部署(我们所有模板演示URL都以.vercel.app结尾)

为什么选择Nextjs

我偏好使用Nextjs,原因如下:

  • 需要后端时提供快速的服务器端API
  • 与Vercel的兼容性
  • 市场接受度高

模板页面结构

每个模板都包含着陆页和以下几个页面:

  • 着陆页
  • 关于页面
  • 博客列表页
  • 博客详情页
  • 订阅页面或表单
  • 法律页面(Cookies、隐私政策、条款条件)
  • 联系页面
  • 服务页面
  • 仪表板
  • 定价页面
  • 订阅表单
  • 登录/注册表单或页面

具体取决于模板类别和用途。例如,为开发者构建AI产品前端网站时,需要除仪表板外的所有页面。仪表板主要用于CRM模板。

项目结构与代码组织

我使用最新版Nextjs,目录结构如下。首先是创建着陆页的各个部分,最简单的着陆页时序如下:

  • 导航栏
  • Hero区域
  • 关于区域
  • 功能/服务区域
  • 客户评价
  • 定价
  • 常见问题
  • 联系表单
  • 包含社交链接的页脚

每个区域都有自己的数据和要渲染的UI,这再次取决于模板。

例如,我们的Eternal AI着陆页有超过5/6个区域。这有助于维护代码。当然,你也可以在一个文件中编辑和添加所有内容,放在section标签内。

由于Nextjs提供基于文件的路由,privacy、cookie和blogs文件夹就是模板的路由——博客页面、Cookie页面和隐私政策页面。

代码示例

Hero区域组件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import { ArrowRight } from "lucide-react";
import { motion } from "framer-motion";

const HeroSection = ({ onOpenLogin }) => {
    return (
        <motion.section
            id="about"
            className="my-16 z-50 scroll-mt-20"
            initial={{ opacity: 0, y: 50 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true, amount: 0.3 }}
            transition={{ duration: 0.6 }}
        >
            <div className="text-center cursor-pointer py-2 px-4 mb-4 justify-center rounded-full border border-zinc-800 bg-gradient-to-tr from-black to-zinc-950/90 text-zinc-400 w-fit mx-auto flex gap-1 items-center">
                <span className="inline-block w-4 h-4 align-middle mr-1">
                    <svg
                        viewBox="0 0 32 32"
                        fill="none"
                        xmlns="http://www.w3.org/2000/svg"
                    >
                        <rect width="32" height="32" rx="7" fill="#FF6600" />
                        <path
                            d="M10.645 8.5h2.105l3.25 6.195L19.197 8.5h1.977l-4.346 8.452V23.5h-1.782v-6.537L10.645 8.5Z"
                            fill="white"
                        />
                    </svg>
                </span>
                <span className="text-xs font-medium">Backed by YC</span>
            </div>
            <br />
            <div className="max-w-5xl mx-auto text-center">
                <h2 className="text-5xl font-extrabold text-zinc-100 mx-auto break-words">
                    Ecosystem for
                    <span className="ml-4 py-4 px-8 relative group rounded-full bg-gradient-to-r from-blue-900 to-transparent ring-4 ring-blue-900 shadow-zinc-700 shadow-xl">
                        AI Agents
                    </span>
                </h2>
                <p className="text-zinc-400 my-8 max-w-md mx-auto">
                    Create intelligent agents to automate, analyze, and make decisions.
                </p>
                <div className="flex gap-4 justify-center my-10">
                    <button
                        onClick={() => onOpenLogin?.()}
                        className="relative flex gap-1 items-center p-2 rounded-full group bg-white text-black hover:text-white transition-all duration-300 ease-in cursor-pointer"
                    >
                        <div className="absolute left-0 bottom-0 w-0 z-0 invisible group-hover:visible transition-all duration-300 ease-in group-hover:w-full bg-blue-800 rounded-full h-full" />
                        <ArrowRight className="w-5 h-5 p-1 bg-blue-800 z-40 group-hover:bg-transparent rounded-full text-zinc-100 transition-all duration-300 ease-in" />
                        <span className="z-50 text-sm">Get Started</span>
                    </button>
                    <button
                        className={`w-fit py-2 px-4 rounded-full text-sm font-medium transition-all duration-300 hover:bg-zinc-200 bg-zinc-950 text-white hover:text-black hover:shadow-xl`}
                    >
                        Learn more
                    </button>
                </div>
            </div>
        </motion.section>
    );
};

export default HeroSection;

博客页面组件

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"use client";

import { useState } from "react";
import Link from "next/link";
import { ArrowRight, Calendar, Clock } from "lucide-react";
import BackgroundDots from "../LandingPage/components/BackgroundDots";
import Navbar from "../LandingPage/components/Navbar";
import MobileMenu from "../LandingPage/components/MobileMenu";
import Footer from "../LandingPage/components/Footer";

// 将标题转换为URL slug的辅助函数
const titleToSlug = (title) => {
    return title
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, "-")
        .replace(/(^-|-$)/g, "");
};

// 示例博客数据
const blogs = [
    {
        title: "The Future of Artificial Intelligence in Business",
        excerpt: "Discover how AI is revolutionizing the way businesses operate, from automation to intelligent decision-making processes that drive unprecedented growth.",
        date: "March 15, 2024",
        readTime: "8 min read",
    },
    // ... 更多博客数据
];

const BlogsPage = () => {
    const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);

    const scrollToSection = (sectionId) => {
        if (typeof window === "undefined") return;

        const element = document.getElementById(sectionId);
        if (element) {
            const offset = 80;
            const elementPosition = element.getBoundingClientRect().top;
            const offsetPosition = elementPosition + window.pageYOffset - offset;

            window.scrollTo({
                top: offsetPosition,
                behavior: "smooth",
            });
        }
    };

    return (
        <div className="relative w-full h-full bg-black min-h-screen">
            <BackgroundDots />

            {/* 背景SVG */}
            <div className="absolute inset-0 top-0 left-0 right-0 bottom-0 opacity-5 pointer-events-none" style={{ zIndex: 0 }}>
                <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" preserveAspectRatio="none" viewBox="0 0 1400 1400">
                    <filter id="a">
                        <feTurbulence type="fractalNoise" baseFrequency=".65" numOctaves="3" stitchTiles="stitch"></feTurbulence>
                    </filter>
                    <rect width="100%" height="100%" filter="url(#a)"></rect>
                </svg>
            </div>

            <Navbar isMobileMenuOpen={isMobileMenuOpen} setIsMobileMenuOpen={setIsMobileMenuOpen} scrollToSection={scrollToSection} />
            <MobileMenu isMobileMenuOpen={isMobileMenuOpen} setIsMobileMenuOpen={setIsMobileMenuOpen} scrollToSection={scrollToSection} />

            <main className="relative z-10 pt-24 pb-20">
                <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
                    <div className="text-center mb-16">
                        <h1 className="text-5xl md:text-6xl font-bold text-white mb-4">Our Blog</h1>
                        <p className="text-xl text-zinc-400 max-w-2xl mx-auto">
                            Insights, tutorials, and stories about technology, design, and innovation
                        </p>
                    </div>

                    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
                        {blogs.map((blog, index) => {
                            const slug = titleToSlug(blog.title);
                            return (
                                <Link key={index} href={`/blogs/${slug}`} className="group">
                                    <article className="bg-gradient-to-r from-black to-zinc-950/60 backdrop-blur-sm hover:shadow-2xl hover:shadow-zinc-800 rounded-2xl p-6 transition-all duration-300 h-full flex flex-col">
                                        <div className="flex-1">
                                            <h2 className="text-2xl font-bold hover:text-white mb-3 text-zinc-200 transition-colors">
                                                {blog.title}
                                            </h2>
                                            <p className="text-zinc-400 mb-4 line-clamp-3">{blog.excerpt}</p>
                                        </div>

                                        <div className="flex items-center justify-between text-sm text-zinc-500 mb-4">
                                            <div className="flex items-center gap-2">
                                                <Calendar className="w-4 h-4" />
                                                <span>{blog.date}</span>
                                            </div>
                                            <div className="flex items-center gap-2">
                                                <Clock className="w-4 h-4" />
                                                <span>{blog.readTime}</span>
                                            </div>
                                        </div>

                                        <div className="flex items-center gap-2 text-zinc-200 group-hover:gap-4 transition-all">
                                            <span className="text-sm font-medium">Read more</span>
                                            <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
                                        </div>
                                    </article>
                                </Link>
                            );
                        })}
                    </div>
                </div>
            </main>

            <Footer />
        </div>
    );
};

export default BlogsPage;

动态路由与博客系统

Nextjs处理动态文件路由,因此slug成为动态URL路由,例如yourdomain.com/blogs/{博客标题}。对于此URL,slug等于"博客标题"。

通过这种方式,可以将所有博客添加到不同的文件中,例如markdown文件内容,并使用动态文件路由进行渲染。这对于生产环境中的服务器端博客SEO非常有用。

样式与动画

Tailwind CSS主要处理从响应式到样式设计、添加主题和颜色的所有内容。运动设计使用framer-motion(framer团队的npm包)或GSAP核心(另一个JavaScript动画库)处理。当然也可以使用anime.js、react-spring或其他库。

对于登录/注册,我们偏好使用相同的基于文件夹的路由,或者主要在叠加层(如模态框)中添加登录/注册表单。

页脚组件示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import { Heater } from "lucide-react";
import Link from "next/link";
import { navigationItems } from "../constants";

const Footer = () => {
    return (
        <footer className="relative w-full bg-gradient-to-r from-black to-zinc-950 border-t border-zinc-800 z-10">
            <div className="w-full mx-auto p-10 bg-gradient-to-r from-black to-zinc-950 grid md:grid-cols-2 grid-cols-1 items-start justify-center transition-all duration-300 ease-in-out">
                <div className="max-w-xl mx-auto ">
                    <Link href="/" className="flex items-center gap-2 mb-2">
                        <Heater className="w-5 h-5 text-white" />
                        <span className="text-white font-semibold text-lg">Eternal</span>
                    </Link>
                    <p className="text-zinc-500 text-xs">
                        © {new Date().getFullYear()} Eternal. All rights reserved.
                    </p>
                </div>

                <div className="grid grid-cols-1 md:grid-cols-3 justify-center md:justify-start gap-8 mb-8">
                    <div>
                        <h5 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider">Navigation</h5>
                        <ul className="space-y-3">
                            {navigationItems.map((item) => (
                                <li key={item.label}>
                                    <Link href={item.href} className="text-zinc-400 hover:text-white transition-colors text-sm">
                                        {item.label}
                                    </Link>
                                </li>
                            ))}
                        </ul>
                    </div>

                    <div>
                        <h5 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider">Legal</h5>
                        <ul className="space-y-3">
                            <li>
                                <Link href="/privacy" className="text-zinc-400 hover:text-white transition-colors text-sm">
                                    Privacy Policy
                                </Link>
                            </li>
                            <li>
                                <a href="#terms" className="text-zinc-400 hover:text-white transition-colors text-sm">
                                    Terms of Service
                                </a>
                            </li>
                            <li>
                                <Link href="/cookie" className="text-zinc-400 hover:text-white transition-colors text-sm">
                                    Cookie Policy
                                </Link>
                            </li>
                            <li>
                                <a href="#security" className="text-zinc-400 hover:text-white transition-colors text-sm">
                                    Security
                                </a>
                            </li>
                        </ul>
                    </div>

                    <div>
                        <h5 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider">Social</h5>
                        <ul className="space-y-3">
                            <li>
                                <a href="https://linkedin.com/company/eternal" target="_blank" rel="noopener noreferrer" className="text-zinc-400 hover:text-blue-400 transition-colors text-sm">
                                    LinkedIn
                                </a>
                            </li>
                            <li>
                                <a href="https://twitter.com/eternal" target="_blank" rel="noopener noreferrer" className="text-zinc-400 hover:text-blue-400 transition-colors text-sm">
                                    Twitter
                                </a>
                            </li>
                            <li>
                                <a href="https://instagram.com/eternal" target="_blank" rel="noopener noreferrer" className="text-zinc-400 hover:text-pink-400 transition-colors text-sm">
                                    Instagram
                                </a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
            <div className="relative h-fit pt-10 text-center rounded-[4em]">
                <p className="md:text-[12em] text-7xl shadow-xlfont-bold font-mono uppercase text-transparent bg-clip-text bg-gradient-to-tr from-black to-white">
                    Eternal
                </p>
            </div>
        </footer>
    );
};

export default Footer;

我偏好使用.map和filter方法,而不是重复硬编码相同的内容两三次——这是编写高性能代码的生产实践。

响应式设计与部署

完成后,我们主要需要检查响应式并使用Tailwind CSS类处理,如md、lg、xl、xxl、2xl、sm、xxs、xs,每个都可以通过名称理解。

如果使用Vercel,部署只需一键操作——只需将仓库推送到GitHub,并将GitHub连接到Vercel以导入和部署仓库,即可在生产环境中快速演示。

请随意尝试以相同的方式构建自己的模板,或者查看我们的模板。

下次见! Shrey

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计