Next.js如何将客户端输入URL传递到服务端执行metadata-scraper解决dns报错
解决方案
你遇到的报错是因为metadata-scraper依赖Node.js原生的dns、http等模块,只能在服务端环境运行,浏览器客户端没有这些底层API,所以需要通过Next.js的服务端API接口中转请求。
步骤1:创建服务端API接口
根据你使用的Next.js路由模式二选一即可:
App Router 实现
在app/api目录下新建scrape-metadata/route.js文件:
import { NextResponse } from 'next/server'; import getMetaData from 'metadata-scraper'; export async function POST(request) { try { const { url } = await request.json(); // 基础合法性校验,避免恶意请求 if (!url || !url.startsWith('http')) { return NextResponse.json({ error: '请输入合法的HTTP/HTTPS链接' }, { status: 400 }); } const metadata = await getMetaData(url); return NextResponse.json(metadata, { status: 200 }); } catch (error) { return NextResponse.json({ error: '元数据抓取失败' }, { status: 500 }); } }
Pages Router 实现
在pages/api目录下新建scrape-metadata.js文件:
import getMetaData from 'metadata-scraper'; export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ error: '仅支持POST请求' }); } try { const { url } = req.body; if (!url || !url.startsWith('http')) { return res.status(400).json({ error: '请输入合法的HTTP/HTTPS链接' }); } const metadata = await getMetaData(url); return res.status(200).json(metadata); } catch (error) { return res.status(500).json({ error: '元数据抓取失败' }); } }
步骤2:修改客户端页面代码
把原代码中直接调用metadata-scraper的逻辑替换为调用刚才创建的API接口,同时修复原有代码的错误:
import { useState } from 'react'; import Input from '@/components/ui/Input'; import Button from '@/components/ui/Button'; // React组件名要求首字母大写,避免被识别为原生HTML标签 const Tree = () => { const [loading, setLoading] = useState(false); const [url, setUrl] = useState(''); const [metadata, setMetadata] = useState(null); const [error, setError] = useState(''); const handleFindUrl = async (e) => { e.preventDefault(); setLoading(true); setError(''); setMetadata(null); try { const res = await fetch('/api/scrape-metadata', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url }), }); if (!res.ok) throw new Error('请求失败'); const data = await res.json(); setMetadata(data); console.log('抓取到的元数据:', data); } catch (err) { setError(err.message); } finally { setLoading(false); } }; return ( <section className="bg-black mb-32 mx-auto max-w-6xl px-6 text-white"> <form onSubmit={handleFindUrl} className="flex flex-col space-y-4"> <Input type="text" placeholder="Link" value={url} // 修复原有onChange错误:需要从事件对象中取输入值 onChange={(e) => setUrl(e.target.value)} required /> <Button variant="slim" type="submit" loading={loading}> 抓取元数据 </Button> </form> {/* 可选:错误提示和元数据展示 */} {error && <p className="text-red-500 mt-4">{error}</p>} {metadata && ( <div className="mt-6 p-4 border border-gray-700 rounded"> <h3>抓取结果:</h3> <p>标题:{metadata.title}</p> <p>描述:{metadata.description}</p> {metadata.image && <img src={metadata.image} alt="预览图" className="mt-2 max-w-xs" />} </div> )} </section> ); } export default Tree;
注意事项
- 可以在服务端接口增加请求频率限制,避免被恶意调用
- 部分网站有反爬机制可能导致抓取失败,可根据需求在服务端增加重试、代理等兼容逻辑
内容的提问来源于stack exchange,提问作者Tom Wicks
相关产品推荐
相关产品推荐

