站长收藏详情

第三课,以博客为例配置strapi后端与next.js对接

时间:2025-03-14   来源:525游



Strapi 后端:
已安装并运行 Strapi(http://localhost:1337)。

已创建 articles 内容类型,包含字段:title(Text)、content(Rich Text)、slug(Text,唯一,选填,用于 SEO 优化)。

已配置好权限:Articles 的 find 和 findOne 权限设置为公开,create 权限需要认证。

示例数据:id: 4, documentId: "nbedxv11g6sdbxvnsdjxs48n", title: "hello world,test2", content: [...]。

Next.js 前端:
已创建 Next.js 项目(位于 C:Nodetest-examplepackageswebsite)。

已安装必要的依赖:axios。


下面我们来操作具体的执行步骤

1. 安装 axios

cd C:Nodetest-examplepackageswebsite

安装 axios  npm install axios  

安装后,检查 node_modules/axios 是否存在。

确认 package.json 中的 dependencies 包含:


"dependencies": {
  "axios": "^1.8.3",
  "next": "15.2.1",
  "react": "^19.0.0",
  "react-dom": "^19.0.0"
}


2、在website 目录下创建以下文件夹和文件:


win10创建文件夹命令符:



# 创建目录
mkdir apparticles apparticles[id] applogin appregister appcreate-article components context utils

# 创建文件
echo. > apparticles[id]page.js
echo. > apploginpage.js
echo. > appregisterpage.js
echo. > appcreate-articlepage.js
echo. > componentsAuthWrapper.js
echo. > componentsNavbar.js
echo. > contextAuthContext.js
echo. > utilsapi.js
echo. > next.config.mjs

运行上述命令后,目录结构应与第 1 步描述一致。app/layout.js 和 app/page.js 由 Next.js 自动生成,其他文件已手动创建。

现在整个目录,应该是下面这样的


test-example/packages/website/
├── app/
│   ├── articles/
│   │   ├── [id]/
│   │   │   └── page.js        # 文章详情页面,动态路由,显示具体文章内容
│   ├── login/
│   │   └── page.js            # 登录页面,处理用户登录
│   ├── register/
│   │   └── page.js            # 注册页面,处理用户注册
│   ├── create-article/
│   │   └── page.js            # 创建文章页面,处理文章发布
│   ├── layout.js              # 根布局文件,包含全局导航
│   └── page.js                # 首页,显示文章列表
├── components/                # 组件目录
│   ├── AuthWrapper.js         # 认证包装组件,提供 AuthContext
│   └── Navbar.js              # 导航组件,动态显示导航链接
├── context/                   # 上下文目录(新建)
│   └── AuthContext.js         # 认证上下文,管理用户状态
├── utils/                     # 工具函数目录
│   └── api.js                 # API 请求工具,封装与 Strapi 的交互
├── public/                    # 静态资源目录(Next.js 默认生成)
│   ├── favicon.ico
│   └── ...
├── styles/                    # 样式目录(Next.js 默认生成)
│   ├── globals.css            # 全局样式
│   └── Home.module.css        # 模块化样式(可选)
├── package.json               # 项目依赖和脚本
├── next.config.mjs            # Next.js 配置文件(新建)
└── README.md                  # 项目说明

文件说明


app/page.js:首页,显示文章列表,调用 getArticles 获取数据。

app/articles/[id]/page.js:文章详情页,动态路由,根据 documentId 获取具体文章。

app/login/page.js:登录页面,处理用户登录。

app/register/page.js:注册页面,处理用户注册。

app/create-article/page.js:创建文章页面,允许登录用户发布文章。

app/layout.js:根布局文件,包含全局导航,使用 AuthWrapper 和 Navbar 组件。

components/AuthWrapper.js:认证包装组件,提供 AuthContext。

components/Navbar.js(新建):导航组件,动态渲染导航链接。

context/AuthContext.js(新建):认证上下文,管理用户登录状态和 token。

utils/api.js:封装 API 请求,与 Strapi 后端交互。

styles/globals.css:全局样式,简单美化页面。

next.config.mjs(新建):Next.js 配置文件,调整配置以支持项目运行。






3. 具体新建文件内容、作用和功能说明

3.1 app/layout.js

作用:根布局文件,定义全局布局,包含导航。


import AuthWrapper from '../components/AuthWrapper';
import Navbar from '../components/Navbar';
import '../styles/globals.css';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <AuthWrapper>
          <Navbar />
          {children}
        </AuthWrapper>
      </body>
    </html>
  );
}

功能说明:
使用 AuthWrapper 组件提供认证上下文。使用 Navbar 组件渲染导航栏。引入全局样式 globals.css。




3.2 app/page.js
作用:首页,显示文章列表


'use client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { getArticles } from '../utils/api';

export default function Home() {
  const [articles, setArticles] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchArticles = async () => {
      try {
        const response = await getArticles();
        console.log('Full API Response:', response);
        console.log('Response Data:', response.data);
        const articlesData = response.data?.data || response.data; // 处理嵌套
        if (Array.isArray(articlesData)) {
          setArticles(articlesData);
        } else {
          setArticles([]);
          setError('No articles data found in API response');
        }
      } catch (err) {
        console.error('Fetch Articles Error:', err);
        setError('Failed to fetch articles');
        setArticles([]);
      } finally {
        setIsLoading(false);
      }
    };
    fetchArticles();
  }, []);

  if (isLoading) {
    return <div className="container">Loading...</div>;
  }

  if (error) {
    return <div className="container">Error: {error}</div>;
  }

  return (
    <div className="container">
      <h1>Articles</h1>
      <div className="article-list">
        {articles.length === 0 ? (
          <p>No articles found.</p>
        ) : (
          articles.map((article) => {
            console.log('Article Object:', article);
            return (
              <div key={article.id} className="article-card">
                <Link href={`/articles/${article.documentId}`}>
                  <h2>{article.title || 'Untitled'}</h2>
                </Link>
                <p>
                  {article.content && article.content[0]?.children && article.content[0].children[0]?.text
                    ? article.content[0].children[0].text.slice(0, 100) + '...'
                    : 'No content available'}
                </p>
              </div>
            );
          })
        )}
      </div>
    </div>
  );
}


功能说明:
调用 getArticles 获取文章列表。使用 useEffect 和 useState 管理数据加载状态。
渲染文章列表,支持点击跳转到文章详情页(使用 documentId 作为参数)。




3.3 app/articles/[id]/page.js
作用:文章详情页,动态路由,显示具体文章内容。
'use client';
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { getArticle } from '../../../utils/api';

export default function ArticleDetail() {
  const params = useParams();
  const documentId = params?.id;
  const [article, setArticle] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const fetchArticle = async () => {
      try {
        const response = await getArticle(documentId);
        console.log('Full Article Response:', response);
        console.log('Response Data:', response.data);
        const articleData = response.data?.data || response.data; // 处理嵌套
        if (articleData) {
          setArticle(articleData);
        } else {
          setArticle(null);
        }
      } catch (err) {
        console.error('Fetch Article Error:', err);
      } finally {
        setIsLoading(false);
      }
    };
    if (documentId) fetchArticle();
  }, [documentId]);

  if (isLoading) {
    return <div className="container">Loading...</div>;
  }

  if (!article) {
    return <div className="container">Article not found.</div>;
  }

  return (
    <div className="container">
      <h1>{article.title || 'Untitled'}</h1>
      <div className="article-content">
        {article.content && Array.isArray(article.content) ? (
          article.content.map((block, index) => (
            <p key={index}>
              {block.children.map((child, childIndex) => (
                <span key={childIndex}>{child.text}</span>
              ))}
            </p>
          ))
        ) : (
          <p>No content available.</p>
        )}
      </div>
    </div>
  );
}


功能说明:
使用 useParams 获取动态路由参数 id(documentId)。调用 getArticle 获取文章详情。渲染文章标题和内容。


3.4 app/login/page.js(新建)
作用:登录页面,处理用户登录。


'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { login } from '../../utils/api';
import { useAuth } from '../../context/AuthContext';

export default function Login() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const router = useRouter();
  const { login: authLogin } = useAuth();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const { data } = await login(email, password);
      authLogin(data.user, data.jwt);
      router.push('/');
    } catch (err) {
      setError('Login failed');
    }
  };

  return (
    <div className="container">
      <h1>Login</h1>
      <form onSubmit={handleSubmit} className="form">
        <div className="form-group">
          <label>Email</label>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            required
          />
        </div>
        <div className="form-group">
          <label>Password</label>
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            required
          />
        </div>
        <button type="submit" className="btn">Login</button>
      </form>
      {error && <p className="error">{error}</p>}
    </div>
  );
}


功能说明:
提供登录表单,输入邮箱和密码。调用 login API,向 Strapi 发送登录请求。
成功后将 JWT 和用户信息存储到 localStorage,并跳转到首页。



3.5 app/register/page.js(新建)
作用:注册页面,处理用户注册。


'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { register } from '../../utils/api';

export default function Register() {
  const [username, setUsername] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const router = useRouter();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const { data } = await register(username, email, password);
      router.push('/login');
    } catch (err) {
      setError('Registration failed');
    }
  };

  return (
    <div className="container">
      <h1>Register</h1>
      <form onSubmit={handleSubmit} className="form">
        <div className="form-group">
          <label>Username</label>
          <input
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
            required
          />
        </div>
        <div className="form-group">
          <label>Email</label>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            required
          />
        </div>
        <div className="form-group">
          <label>Password</label>
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            required
          />
        </div>
        <button type="submit" className="btn">Register</button>
      </form>
      {error && <p className="error">{error}</p>}
    </div>
  );
}

功能说明:
提供注册表单,输入用户名、邮箱和密码。调用 register API,向 Strapi 发送注册请求。
成功后存储 JWT 和用户信息,跳转到首页。


3.6 app/create-article/page.js(新建)
作用:创建文章页面,允许登录用户发布文章。


'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { createArticle } from '../../utils/api';
import { useAuth } from '../../context/AuthContext';

export default function CreateArticle() {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [error, setError] = useState('');
  const { token } = useAuth();
  const router = useRouter();

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!token) {
      setError('Please login to create an article');
      return;
    }
    try {
      const articleData = {
        title,
        content: [
          {
            type: 'paragraph',
            children: [
              {
                type: 'text',
                text: content || 'Default content',
              },
            ],
          },
        ],
        publishedAt: new Date().toISOString(),
      };
      await createArticle(token, articleData);
      router.push('/');
    } catch (err) {
      setError('Failed to create article');
    }
  };

  return (
    <div className="container">
      <h1>Create Article</h1>
      <form onSubmit={handleSubmit} className="form">
        <div className="form-group">
          <label>Title</label>
          <input
            type="text"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            required
          />
        </div>
        <div className="form-group">
          <label>Content</label>
          <textarea
            value={content}
            onChange={(e) => setContent(e.target.value)}
            required
            rows="5"
          />
        </div>
        <button type="submit" className="btn">Publish</button>
      </form>
      {error && <p className="error">{error}</p>}
    </div>
  );
}


功能说明:
提供文章发布表单,输入标题和内容。检查用户是否登录(通过 token)。
调用 createArticle API,向 Strapi 发送文章创建请求。成功后跳转到首页。




3.7 components/AuthWrapper.js(新建)
作用:认证包装组件,提供 AuthContext。

'use client';
import { AuthProvider } from '../context/AuthContext';

export default function AuthWrapper({ children }) {
  return <AuthProvider>{children}</AuthProvider>;
}

功能说明:
使用 AuthProvider 提供认证上下文,包装所有页面内容。
确保子组件可以通过 useAuth 访问用户状态和 token。


3.8 components/Navbar.js(新建)
作用:导航组件,动态渲染导航链接。




'use client';
import Link from 'next/link';
import { useAuth } from '../context/AuthContext';

export default function Navbar() {
  const { user, logout } = useAuth();

  return (
    <nav className="navbar">
      <Link href="/">Home</Link>
      {user ? (
        <>
          <Link href="/create-article">Create Article</Link>
          <button onClick={logout} className="btn">Logout</button>
          <span>Welcome, {user.username}</span>
        </>
      ) : (
        <>
          <Link href="/login">Login</Link>
          <Link href="/register">Register</Link>
        </>
      )}
    </nav>
  );
}


功能说明:
使用 useAuth 获取用户状态和 logout 方法。
动态渲染导航:未登录显示“登录”和“注册”,已登录显示“创建文章”、“登出”和欢迎信息。



3.9 context/AuthContext.js(新建)
作用:认证上下文,管理用户登录状态和 token。


'use client';
import { createContext, useContext, useState, useEffect } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [token, setToken] = useState(null);

  useEffect(() => {
    if (typeof window !== 'undefined') {
      const storedToken = localStorage.getItem('token');
      const storedUser = localStorage.getItem('user');
      if (storedToken && storedUser) {
        setToken(storedToken);
        setUser(JSON.parse(storedUser));
      }
    }
  }, []);

  const login = (userData, jwt) => {
    setUser(userData);
    setToken(jwt);
    if (typeof window !== 'undefined') {
      localStorage.setItem('token', jwt);
      localStorage.setItem('user', JSON.stringify(userData));
    }
  };

  const logout = () => {
    setUser(null);
    setToken(null);
    if (typeof window !== 'undefined') {
      localStorage.removeItem('token');
      localStorage.removeItem('user');
    }
  };

  return (
    <AuthContext.Provider value={{ user, token, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext);




功能说明:
创建 AuthContext,管理用户状态(user 和 token)。

useEffect:在客户端加载时检查 localStorage,恢复用户状态。

login:保存用户数据和 token 到状态和 localStorage。

logout:清除用户状态和 localStorage。

useAuth:提供钩子,供其他组件访问上下文。



3.10 utils/api.js
作用:封装 API 请求,与 Strapi 后端交互。


import axios from 'axios';

const API_URL = 'http://localhost:1337/api';

export const register = async (username, email, password) => {
  return axios.post(`${API_URL}/auth/local/register`, {
    username,
    email,
    password,
  });
};

export const login = async (email, password) => {
  return axios.post(`${API_URL}/auth/local`, {
    identifier: email,
    password,
  });
};

export const getArticles = async () => {
  return axios.get(`${API_URL}/articles?populate=*`);
};

export const getArticle = async (documentId) => {
  return axios.get(`${API_URL}/articles/${documentId}?populate=*`);
};

export const createArticle = async (token, articleData) => {
  return axios.post(
    `${API_URL}/articles`,
    { data: articleData },
    { headers: { Authorization: `Bearer ${token}` } }
  );
};


功能说明:
register:向 Strapi 发送注册请求。login:向 Strapi 发送登录请求。

getArticles:获取文章列表。getArticle:获取单篇文章(使用 documentId)。

createArticle:创建新文章(需要认证)。




3.11 styles/globals.css
作用:全局样式,简单美化页面。




body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
  background-color: #f5f5f5;
}

.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.navbar {
  display: flex;
  gap: 20px;
  padding: 10px 20px;
  background-color: #0070f3;
  color: white;
}

.navbar a {
  color: white;
  text-decoration: none;
}

.navbar button {
  background: none;
  border: none;
  color: white;
  cursor: pointer;
}

.form {
  display: flex;
  flex-direction: column;
  gap: 15px;
  max-width: 400px;
  margin: 0 auto;
}

.form-group {
  display: flex;
  flex-direction: column;
}

.form-group label {
  margin-bottom: 5px;
  font-weight: bold;
}

.form-group input,
.form-group textarea {
  padding: 8px;
  font-size: 16px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.btn {
  padding: 10px;
  background-color: #0070f3;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

.btn:hover {
  background-color: #005bb5;
}

.error {
  color: red;
  margin-top: 10px;
}

.article-list {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.article-card {
  padding: 15px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.article-card h2 {
  margin: 0 0 10px;
  color: #0070f3;
}

.article-content p {
  margin: 10px 0;
}


功能说明:
提供全局样式,美化页面布局、导航、表单和文章展示。






3.12 next.config.mjs(新建)
作用:Next.js 配置文件,调整项目配置。


/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
};

export default nextConfig;




功能说明:
启用 reactStrictMode,确保 React 严格模式运行,帮助发现潜在问题。

可根据需要添加其他配置(如图像优化、环境变量等)。




4.配置完毕以后,npm run dev 启动strapi 与next.js 然后测试下面的!


注册:访问 http://localhost:3000/register,输入用户名、邮箱和密码,注册用户。

登录:访问 http://localhost:3000/login,输入邮箱和密码,登录。

文章列表:访问 http://localhost:3000/,查看文章列表。

文章详情:点击文章标题,访问 http://localhost:3000/articles/nbedxv11g6sdbxvnsdjxs48n,查看文章详情。

发布文章:登录后访问 http://localhost:3000/create-article,创建新文章。


好的。下面已经打包备份了。strapi cms 和next.js website 文件中修改的资料;可以查找对比。

 

下地地址   https://www.525you.com/d/file/2025/202503/f588af66fa52f6c2e80446d0fdfbreb1.rar

 

 

相关阅读

精彩推荐