情境大概是想用SPA (single page application) 蓋網站
但是spa無法印出各各連結的meta
正常情況例如A商品 要帶出A商品的title, description, image等等到meta
這樣貼到facebook或是line的時候才會有標題 描述 圖片預覽
spa辦不到噗
所以據小弟所知spa比較適合蓋後台 不適合蓋前台噗
也因為這樣 才會有NUXT跟NEXT這種framework跑出來吧噗
有SSR (server side render) 所以可以印出每頁的html跟他所帶有的meta
缺點就是 跑第一頁的時候 會在server先跑出結果 再把html噴出來給client
跟spa不一樣
spa只要一直吐出一模一樣的html server不用處理任何東西
所有動作都交給clinet端處理
以上是前言XDXDXDXD
以下就是小弟的解法 SPA有SEO的做法噗
解法1就是用3方服務XD
用量太多要錢的XD
小弟是用nginx
設置滿簡單的照著他設定就好
原理是遇到某些User-Agent時 要proxy過去給他
然後他會把網址完整跑過一次 再把html吐出來
小弟測試是work的 覺得很神奇 於是參考了他nginx的作法
然後就是小弟自製的解法2
nginx部分大大參考了 prerender.io提供的設置 遇到某些User-Agent
就proxy到localhost的某個位置
再來是nodejs部分
做法是用puppeteer
讓他在背景跑一個chrome 然後塞網址給他 然後等他跑完 再把html吐出來
為了加快速度 小弟加上redis跟mysql噗
puppeteer在小弟電腦跑是滿快的 但在aws的ubuntu免費方案主機跑是有點慢噗
所以滿需要cache的 再來是很可能要有排程 定期自動刷新商品頁(如果是購物網站的話)
可以自訂要刷新的頻率 幾乎沒再動的頁面可能一周一次
而商品可以在有更動的時候寫入一個狀態 讓排程抓這個狀態的商品 再fetcth一次
然後一樣塞進mysql跟redis噗
這是DB table prerender
大概就是 url 配html的表
裡面有段要注意的是puppeteer的launch
小弟是windows 這樣寫就好了
const browser = await puppeteer.launch();
但是在ubuntu要這樣寫噗
const browser = await puppeteer.launch({
executablePath: '/usr/bin/chromium-browser',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
然後ubuntu要額外安裝chrome的東西 沒意外應該是這兩個指令
sudo apt-get install chromium-browser
sudo apt-get install xdg-utils
可以參考看看人家的文章噗
然後是小弟的這包專案噗
測試方法
專案跑起來後
隨便準備一個spa 能的話弄個一頁是有改title的
然後用可以帶User-Agent的工具測試
小弟是用postman噗
只要在headers多帶個User-Agent裡面填facebookexternalhit/1.1
方法GET 沒意外會proxy到主機的localhost:5566
nodejs收到後會就會開始跑puppeteer跑出html再吐出來
小弟實際測試 可以在facebook跟line正確跑出結果
以下是nginx的設定噗 大概有87%是參考prerender.io提供的設定XD
#prerender
map $http_user_agent $prerender_ua {
default 0;
"~*Prerender" 0;
"~*googlebot" 1;
"~*yahoo!\ slurp" 1;
"~*bingbot" 1;
"~*yandex" 1;
"~*baiduspider" 1;
"~*facebookexternalhit" 1;
"~*twitterbot" 1;
"~*rogerbot" 1;
"~*linkedinbot" 1;
"~*embedly" 1;
"~*quora\ link\ preview" 1;
"~*showyoubot" 1;
"~*outbrain" 1;
"~*pinterest\/0\." 1;
"~*developers.google.com\/\+\/web\/snippet" 1;
"~*slackbot" 1;
"~*vkshare" 1;
"~*w3c_validator" 1;
"~*redditbot" 1;
"~*applebot" 1;
"~*whatsapp" 1;
"~*flipboard" 1;
"~*tumblr" 1;
"~*bitlybot" 1;
"~*skypeuripreview" 1;
"~*nuzzel" 1;
"~*discordbot" 1;
"~*google\ page\ speed" 1;
"~*qwantify" 1;
"~*pinterestbot" 1;
"~*bitrix\ link\ preview" 1;
"~*xing-contenttabreceiver" 1;
"~*chrome-lighthouse" 1;
"~*telegrambot" 1;
}
map $args $prerender_args {
default $prerender_ua;
"~(^|&)_escaped_fragment_=" 1;
}
map $http_x_prerender $x_prerender {
default $prerender_args;
"1" 0;
}
map $uri $prerender {
default $x_prerender;
"~*\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)" 0;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
root /xxxxxxxxxxxx/xxxxxxxx/dist;
set $index "index.html";
index $index;
charset utf-8;
location / {
if ($prerender = 1) {
rewrite (.*) /prerender/yada last;
}
try_files $uri /index.html = 404;
}
location /prerender/yada {
if ($prerender = 0) {
return 404;
}
set $args "url=$scheme://$host$request_uri";
proxy_hide_header Cache-Control;
add_header Cache-Control "private,max-age=600,must-revalidate";
proxy_pass http://localhost:5566;
}
}
大概就是醬子噗
有這做法 小弟應該沒意外不會再主動用nuxt或next這種有ssr的framework去開發專案
而是直接用spa蓋就好了嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷
但小弟很肯定大部分的專案小弟還是會用php laravel蓋
個人私心覺得laravel蓋網站真的很好蓋嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷