關於SPA網站的SEO解法XD



情境大概是想用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蓋網站真的很好蓋嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷嗷


沒有留言: