Bagaimana cara membuat SEO SEO merangkak?

143

Saya telah bekerja tentang cara membuat SPA dapat dirayapi oleh google berdasarkan instruksi google . Meskipun ada beberapa penjelasan umum, saya tidak dapat menemukan tutorial langkah-demi-langkah yang lebih teliti dengan contoh-contoh aktual. Setelah menyelesaikan ini saya ingin membagikan solusi saya sehingga orang lain juga dapat memanfaatkannya dan mungkin memperbaikinya lebih lanjut.
Saya menggunakan MVCdengan Webapipengontrol, dan Phantomjs di sisi server, dan Durandal di sisi klien dengan push-statediaktifkan; Saya juga menggunakan Breezejs untuk interaksi data client-server, semuanya sangat saya sarankan, tapi saya akan mencoba memberikan penjelasan yang cukup umum yang juga akan membantu orang menggunakan platform lain.

balok
sumber
40
mengenai "off topic" - seorang programmer aplikasi web harus menemukan cara bagaimana membuat aplikasinya dapat dijelajahi untuk SEO, ini adalah persyaratan dasar di web. Melakukan ini bukan tentang pemrograman semata, tetapi relevan dengan subjek "masalah praktis yang dapat dijawab yang unik untuk profesi pemrograman" seperti yang dijelaskan dalam stackoverflow.com/help/on-topic . Ini adalah masalah bagi banyak programmer tanpa solusi yang jelas di seluruh web. Saya berharap untuk membantu orang lain dan berinvestasi berjam-jam hanya dengan menggambarkannya di sini, mendapatkan poin negatif tentu tidak memotivasi saya untuk membantu lagi.
beamish
3
Jika penekanannya adalah pada pemrograman dan bukan minyak ular / saus rahasia SEO voodoo / spam maka itu bisa menjadi topikal sempurna. Kami juga menyukai jawaban sendiri di mana mereka memiliki potensi untuk berguna bagi pembaca jangka panjang di masa depan. Pasangan tanya jawab ini tampaknya lulus kedua tes tersebut. (Beberapa detail latar belakang bisa menyempurnakan pertanyaan lebih baik daripada diperkenalkan dalam jawaban tapi itu cukup kecil)
Flexo
6
+1 untuk mengurangi suara. Terlepas dari apakah q / a akan lebih cocok sebagai posting blog, pertanyaannya relevan dengan Durandal dan jawabannya diteliti dengan baik.
RainerAtSpirit
2
Saya setuju bahwa SEO adalah bagian penting dari kehidupan pengembang saat ini dan harus dipertimbangkan sebagai topik di stackoverflow!
Kim D.
Selain menerapkan seluruh proses sendiri, Anda dapat mencoba SnapSearch snapsearch.io yang pada dasarnya mengatasi masalah ini sebagai layanan.
CMCDragonkai

Jawaban:

121

Sebelum memulai, pastikan Anda memahami apa yang dibutuhkan Google , khususnya penggunaan URL yang cantik dan jelek . Sekarang mari kita lihat implementasinya:

Sisi klien

Di sisi klien Anda hanya memiliki satu halaman html yang berinteraksi dengan server secara dinamis melalui panggilan AJAX. itu tentang SPA. Semua atag di sisi klien dibuat secara dinamis di aplikasi saya, nanti kita akan melihat bagaimana membuat tautan ini terlihat oleh bot google di server. Setiap seperti akebutuhan tag untuk dapat memiliki pretty URLdalam hreftag sehingga bot bahwa google akan merangkak itu. Anda tidak ingin hrefbagian itu digunakan ketika klien mengkliknya (meskipun Anda ingin server dapat menguraikannya, kami akan melihatnya nanti), karena kami mungkin tidak ingin halaman baru dimuat, hanya untuk membuat panggilan AJAX mendapatkan beberapa data untuk ditampilkan di bagian halaman dan mengubah URL melalui javascript (misalnya menggunakan HTML5 pushstateatau dengan Durandaljs). Jadi, kami memiliki keduanyahrefatribut untuk google dan juga onclickyang melakukan pekerjaan ketika pengguna mengklik tautan. Sekarang, karena saya menggunakan push-statesaya tidak ingin ada #di URL, jadi atag khas mungkin terlihat seperti ini:
<a href="http://www.xyz.com/#!/category/subCategory/product111" onClick="loadProduct('category','subCategory','product111')>see product111...</a>

'kategori' dan 'subkategori' mungkin akan menjadi frasa lain, seperti 'komunikasi' dan 'telepon' atau 'komputer' dan 'laptop' untuk toko peralatan listrik. Jelas akan ada banyak kategori dan sub kategori yang berbeda. Seperti yang Anda lihat, tautan langsung ke kategori, sub kategori dan produk, bukan sebagai parameter tambahan ke laman 'toko' tertentu seperti http://www.xyz.com/store/category/subCategory/product111. Ini karena saya lebih suka tautan yang lebih pendek dan lebih sederhana. Ini menyiratkan bahwa saya tidak akan ada kategori dengan nama yang sama dengan salah satu 'halaman' saya, yaitu '
Saya tidak akan membahas cara memuat data melalui AJAX ( onclickbagian), mencarinya di google, ada banyak penjelasan bagus. Satu-satunya hal penting di sini yang ingin saya sebutkan adalah ketika pengguna mengklik tautan ini, saya ingin URL di browser terlihat seperti ini:
http://www.xyz.com/category/subCategory/product111. Dan ini URL tidak dikirim ke server! ingat, ini adalah SPA di mana semua interaksi antara klien dan server dilakukan melalui AJAX, tidak ada tautan sama sekali! semua 'halaman' diimplementasikan pada sisi klien, dan URL yang berbeda tidak membuat panggilan ke server (server tidak perlu tahu bagaimana menangani URL ini jika mereka digunakan sebagai tautan eksternal dari situs lain ke situs Anda, kita akan melihatnya nanti di bagian sisi server). Sekarang, ini ditangani dengan luar biasa oleh Durandal. Saya sangat merekomendasikannya, tetapi Anda juga dapat melewati bagian ini jika Anda lebih suka teknologi lainnya. Jika Anda memilihnya, dan Anda juga menggunakan MS Visual Studio Express 2012 untuk Web seperti saya, Anda dapat menginstal Durandal Starter Kit , dan di sana, di shell.js, gunakan sesuatu seperti ini:

define(['plugins/router', 'durandal/app'], function (router, app) {
    return {
        router: router,
        activate: function () {
            router.map([
                { route: '', title: 'Store', moduleId: 'viewmodels/store', nav: true },
                { route: 'about', moduleId: 'viewmodels/about', nav: true }
            ])
                .buildNavigationModel()
                .mapUnknownRoutes(function (instruction) {
                    instruction.config.moduleId = 'viewmodels/store';
                    instruction.fragment = instruction.fragment.replace("!/", ""); // for pretty-URLs, '#' already removed because of push-state, only ! remains
                    return instruction;
                });
            return router.activate({ pushState: true });
        }
    };
});

Ada beberapa hal penting yang perlu diperhatikan di sini:

  1. Rute pertama (dengan route:'') adalah untuk URL yang tidak memiliki data tambahan di dalamnya, yaitu http://www.xyz.com. Di halaman ini Anda memuat data umum menggunakan AJAX. Sebenarnya tidak ada atag sama sekali di halaman ini. Anda akan ingin menambahkan tag berikut sehingga bot bahwa google akan tahu apa yang harus dilakukan dengan itu:
    <meta name="fragment" content="!">. Tag ini akan membuat bot google mengubah URL www.xyz.com?_escaped_fragment_=yang akan kita lihat nanti.
  2. Rute 'tentang' hanyalah contoh tautan ke 'laman' lain yang mungkin Anda inginkan di aplikasi web Anda.
  3. Sekarang, bagian yang sulit adalah tidak ada rute 'kategori', dan mungkin ada banyak kategori berbeda - tidak ada yang memiliki rute yang telah ditentukan. Di sinilah mapUnknownRoutesmasuk. Ini memetakan rute yang tidak diketahui ini ke rute 'toko' dan juga menghapus semua '!' dari URL jika itu pretty URLdihasilkan oleh mesin pencari google. Rute 'toko' mengambil info di properti 'fragmen' dan membuat panggilan AJAX untuk mendapatkan data, menampilkannya, dan mengubah URL secara lokal. Dalam aplikasi saya, saya tidak memuat halaman yang berbeda untuk setiap panggilan seperti itu; Saya hanya mengubah bagian halaman di mana data ini relevan dan juga mengubah URL secara lokal.
  4. Perhatikan pushState:trueyang menginstruksikan Durandal untuk menggunakan URL push state.

Ini semua yang kami butuhkan di sisi klien. Itu dapat diimplementasikan juga dengan URL hash (di Durandal Anda cukup menghapusnya pushState:true). Bagian yang lebih kompleks (setidaknya untuk saya ...) adalah bagian server:

Sisi server

Saya menggunakan MVC 4.5di sisi server dengan WebAPIpengontrol. Server sebenarnya perlu menangani 3 jenis URL: yang dihasilkan oleh google - keduanya prettydan uglydan juga URL 'sederhana' dengan format yang sama dengan yang muncul di browser klien. Mari kita lihat bagaimana melakukan ini:

URL yang cantik dan yang 'sederhana' pertama kali ditafsirkan oleh server seolah mencoba mereferensikan pengontrol yang tidak ada. Server melihat sesuatu seperti http://www.xyz.com/category/subCategory/product111dan mencari pengontrol bernama 'kategori'. Jadi, web.configsaya menambahkan baris berikut untuk mengarahkan ini ke controller penanganan kesalahan tertentu:

<customErrors mode="On" defaultRedirect="Error">
    <error statusCode="404" redirect="Error" />
</customErrors><br/>

Sekarang, ini mengubah URL ke sesuatu seperti: http://www.xyz.com/Error?aspxerrorpath=/category/subCategory/product111. Saya ingin URL dikirim ke klien yang akan memuat data melalui AJAX, jadi triknya di sini adalah memanggil pengontrol 'indeks' default seolah-olah tidak mereferensikan pengontrol apa pun; Saya melakukannya dengan menambahkan hash ke URL sebelum semua parameter 'kategori' dan 'subkategori'; URL hash tidak memerlukan pengontrol khusus kecuali pengontrol 'indeks' default dan data dikirim ke klien yang kemudian menghapus hash dan menggunakan info setelah hash untuk memuat data melalui AJAX. Berikut adalah kode pengendali penangan kesalahan:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

using System.Web.Routing;

namespace eShop.Controllers
{
    public class ErrorController : ApiController
    {
        [HttpGet, HttpPost, HttpPut, HttpDelete, HttpHead, HttpOptions, AcceptVerbs("PATCH"), AllowAnonymous]
        public HttpResponseMessage Handle404()
        {
            string [] parts = Request.RequestUri.OriginalString.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries);
            string parameters = parts[ 1 ].Replace("aspxerrorpath=","");
            var response = Request.CreateResponse(HttpStatusCode.Redirect);
            response.Headers.Location = new Uri(parts[0].Replace("Error","") + string.Format("#{0}", parameters));
            return response;
        }
    }
}


Tapi bagaimana dengan URL Jelek ? Ini dibuat oleh bot google dan harus mengembalikan HTML biasa yang berisi semua data yang dilihat pengguna di browser. Untuk ini saya menggunakan phantomjs . Phantom adalah peramban tanpa kepala yang melakukan peramban di sisi klien - tetapi di sisi server. Dengan kata lain, hantu tahu (antara lain) cara mendapatkan halaman web melalui URL, menguraikannya termasuk menjalankan semua kode javascript di dalamnya (serta mendapatkan data melalui panggilan AJAX), dan memberikan Anda kembali HTML yang mencerminkan DOM. Jika Anda menggunakan MS Visual Studio Express Anda banyak keinginan untuk menginstal hantu melalui ini Link .
Tapi pertama-tama, ketika URL jelek dikirim ke server, kita harus menangkapnya; Untuk ini, saya menambahkan ke folder 'App_start' file berikut:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace eShop.App_Start
{
    public class AjaxCrawlableAttribute : ActionFilterAttribute
    {
        private const string Fragment = "_escaped_fragment_";

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var request = filterContext.RequestContext.HttpContext.Request;

            if (request.QueryString[Fragment] != null)
            {

                var url = request.Url.ToString().Replace("?_escaped_fragment_=", "#");

                filterContext.Result = new RedirectToRouteResult(
                    new RouteValueDictionary { { "controller", "HtmlSnapshot" }, { "action", "returnHTML" }, { "url", url } });
            }
            return;
        }
    }
}

Ini disebut dari 'filterConfig.cs' juga di 'App_start':

using System.Web.Mvc;
using eShop.App_Start;

namespace eShop
{
    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            filters.Add(new AjaxCrawlableAttribute());
        }
    }
}

Seperti yang Anda lihat, 'AjaxCrawlableAttribute' merutekan URL yang jelek ke pengontrol bernama 'HtmlSnapshot', dan inilah pengontrol ini:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace eShop.Controllers
{
    public class HtmlSnapshotController : Controller
    {
        public ActionResult returnHTML(string url)
        {
            string appRoot = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);

            var startInfo = new ProcessStartInfo
            {
                Arguments = String.Format("{0} {1}", Path.Combine(appRoot, "seo\\createSnapshot.js"), url),
                FileName = Path.Combine(appRoot, "bin\\phantomjs.exe"),
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                RedirectStandardInput = true,
                StandardOutputEncoding = System.Text.Encoding.UTF8
            };
            var p = new Process();
            p.StartInfo = startInfo;
            p.Start();
            string output = p.StandardOutput.ReadToEnd();
            p.WaitForExit();
            ViewData["result"] = output;
            return View();
        }

    }
}

Yang terkait viewsangat sederhana, hanya satu baris kode:
@Html.Raw( ViewBag.result )
Seperti yang dapat Anda lihat di controller, phantom memuat file javascript bernama di createSnapshot.jsbawah folder yang saya buat bernama seo. Ini adalah file javascript ini:

var page = require('webpage').create();
var system = require('system');

var lastReceived = new Date().getTime();
var requestCount = 0;
var responseCount = 0;
var requestIds = [];
var startTime = new Date().getTime();

page.onResourceReceived = function (response) {
    if (requestIds.indexOf(response.id) !== -1) {
        lastReceived = new Date().getTime();
        responseCount++;
        requestIds[requestIds.indexOf(response.id)] = null;
    }
};
page.onResourceRequested = function (request) {
    if (requestIds.indexOf(request.id) === -1) {
        requestIds.push(request.id);
        requestCount++;
    }
};

function checkLoaded() {
    return page.evaluate(function () {
        return document.all["compositionComplete"];
    }) != null;
}
// Open the page
page.open(system.args[1], function () { });

var checkComplete = function () {
    // We don't allow it to take longer than 5 seconds but
    // don't return until all requests are finished
    if ((new Date().getTime() - lastReceived > 300 && requestCount === responseCount) || new Date().getTime() - startTime > 10000 || checkLoaded()) {
        clearInterval(checkCompleteInterval);
        var result = page.content;
        //result = result.substring(0, 10000);
        console.log(result);
        //console.log(results);
        phantom.exit();
    }
}
// Let us check to see if the page is finished rendering
var checkCompleteInterval = setInterval(checkComplete, 300);

Pertama saya ingin mengucapkan terima kasih kepada Thomas Davis untuk halaman di mana saya mendapatkan kode dasar dari :-).
Anda akan melihat sesuatu yang aneh di sini: hantu terus memuat ulang halaman sampai checkLoaded()fungsinya kembali benar. Mengapa demikian? ini karena SPA spesifik saya membuat beberapa panggilan AJAX untuk mendapatkan semua data dan menempatkannya di DOM di halaman saya, dan hantu tidak tahu kapan semua panggilan telah selesai sebelum mengembalikan saya kembali refleksi HTML dari DOM. Apa yang saya lakukan di sini adalah setelah panggilan AJAX terakhir saya tambahkan <span id='compositionComplete'></span>, sehingga jika tag ini ada saya tahu DOM selesai. Saya melakukan ini sebagai respons terhadap compositionCompleteacara Durandal , lihat di siniuntuk lebih. Jika ini tidak terjadi dalam 10 detik, saya menyerah (seharusnya hanya membutuhkan waktu satu detik hingga yang paling banyak). HTML yang dikembalikan berisi semua tautan yang dilihat pengguna di peramban. Script tidak akan berfungsi dengan baik karena <script>tag yang ada di snapshot HTML tidak mereferensikan URL yang tepat. Ini dapat diubah juga di file phantom javascript, tapi saya rasa ini tidak perlu karena HTML snapshort hanya digunakan oleh google untuk mendapatkan atautan dan bukan untuk menjalankan javascript; link ini melakukan referensi URL yang cukup, dan jika fakta, jika Anda mencoba untuk melihat snapshot HTML di browser, Anda akan mendapatkan error javascript tapi semua link akan bekerja dengan baik dan mengarahkan Anda ke server sekali lagi dengan URL yang cukup saat ini dapatkan halaman yang sepenuhnya berfungsi.
Ini dia. Sekarang server tahu cara menangani URL cantik dan jelek, dengan push-state diaktifkan di server dan klien. Semua URL jelek diperlakukan dengan cara yang sama menggunakan hantu sehingga tidak perlu membuat pengontrol terpisah untuk setiap jenis panggilan.
Satu hal yang mungkin lebih memilih untuk perubahan tidak untuk membuat panggilan umum 'kategori / subkategori / produk' tetapi menambahkan 'toko' sehingga link akan terlihat seperti: http://www.xyz.com/store/category/subCategory/product111. Ini akan menghindari masalah dalam solusi saya bahwa semua URL yang tidak valid diperlakukan seolah-olah mereka benar-benar panggilan ke pengontrol 'indeks', dan saya kira ini dapat ditangani kemudian di dalam pengontrol 'toko' tanpa tambahan pada yang web.configsaya perlihatkan di atas. .

balok
sumber
Saya punya pertanyaan singkat, saya pikir saya sudah bisa bekerja sekarang, tetapi ketika saya mengirimkan situs saya ke google, dan memberikan tautan ke google, peta situs, dll apakah saya harus memberikan google mysite.com/# ! atau hanya mysite.com dan google akan menambahkan di escaped_fragment karena saya memilikinya di meta tag?
ccorrin
ccorrin - sepengetahuan saya Anda tidak perlu memberikan google apa pun; bot google akan menemukan situs Anda dan mencarinya di URL yang cantik (jangan lupa di beranda untuk menambahkan tag meta juga, karena mungkin tidak mengandung URL apa pun). URL jelek yang berisi escaped_fragment selalu ditambahkan hanya oleh google - Anda tidak boleh memasukkannya sendiri ke dalam HTML Anda. dan terima kasih atas dukungannya :-)
beamish
terima kasih Bjorn & Sandra :-) Saya sedang mengerjakan versi yang lebih baik dari dokumen ini, yang juga akan menyertakan info tentang cara cache halaman sehingga membuat proses lebih cepat dan melakukannya dalam penggunaan yang lebih umum di mana url mengandung nama pengontrol; Saya akan mempostingnya segera setelah siap
beamish
Ini penjelasan yang bagus !!. Saya menerapkannya dan bekerja seperti pesona di devbox localhost saya. Masalahnya adalah ketika digunakan untuk Situs Web Azure karena situs tersebut macet dan setelah beberapa saat saya mendapatkan kesalahan 502. Apakah Anda punya ide tentang cara menyebarkan phantomjs ke Azure ?? ... Terima kasih ( testypv.azurewebsites.net/?_escaped_fragment_=home/about )
yagopv
Saya tidak punya pengalaman dengan situs web Azure, tetapi yang terlintas di benak saya adalah bahwa mungkin proses pemeriksaan untuk memuat halaman sepenuhnya tidak pernah terpenuhi sehingga server terus berusaha memuat ulang halaman itu lagi dan lagi tanpa hasil. mungkin di situlah masalahnya (meskipun ada batas waktu untuk pemeriksaan ini sehingga mungkin tidak ada di sana)? cobalah untuk menempatkan 'mengembalikan yang benar;' sebagai baris pertama di 'checkLoaded ()' dan lihat apakah ada bedanya.
beamish
4

Berikut ini tautan ke rekaman screencast dari kelas Pelatihan Ember.js yang saya selenggarakan di London pada 14 Agustus. Ini menguraikan strategi untuk aplikasi sisi klien Anda dan untuk aplikasi sisi server Anda, serta memberikan demonstrasi langsung tentang bagaimana menerapkan fitur-fitur ini akan memberikan JavaScript Single-Page-App Anda degradasi anggun bahkan untuk pengguna dengan JavaScript dimatikan .

Ini menggunakan PhantomJS untuk membantu merayapi situs web Anda.

Singkatnya, langkah-langkah yang diperlukan adalah:

  • Memiliki versi host aplikasi web yang ingin Anda jelajahi, situs ini harus memiliki SEMUA data yang Anda miliki dalam produksi
  • Tulis aplikasi JavaScript (Skrip PhantomJS) untuk memuat situs web Anda
  • Tambahkan index.html (atau “/“) ke daftar URL yang akan dirayapi
    • Pop URL pertama yang ditambahkan ke daftar crawl
    • Muat halaman dan render DOM-nya
    • Temukan tautan apa pun di laman yang dimuat yang menaut ke situs Anda sendiri (penyaringan URL)
    • Tambahkan tautan ini ke daftar URL "yang dapat dijelajahi", jika belum dirayapi
    • Simpan DOM yang diberikan ke file pada sistem file, tetapi hapus SEMUA tag-skrip terlebih dahulu
    • Pada akhirnya, buat file Sitemap.xml dengan URL yang dirayapi

Setelah langkah ini selesai, terserah backend Anda untuk menyajikan versi statis HTML Anda sebagai bagian dari noscript-tag pada halaman tersebut. Ini akan memungkinkan Google dan mesin pencari lainnya untuk merayapi setiap halaman di situs web Anda, meskipun aplikasi Anda pada awalnya adalah aplikasi satu halaman.

Tautan ke screencast dengan detail lengkap:

http://www.devcasts.io/p/spas-phantomjs-and-seo/#

Joachim H. Skeie
sumber
0

Anda dapat menggunakan atau membuat layanan Anda sendiri untuk prerender SPA Anda dengan layanan yang disebut prerender. Anda dapat memeriksanya di prerender.io situs webnya dan pada proyek github- nya (Menggunakan PhantomJS dan merender situs web Anda untuk Anda).

Sangat mudah untuk memulai. Anda hanya perlu mengarahkan permintaan crawler ke layanan dan mereka akan menerima html yang diberikan.

gabrielperales
sumber
2
Meskipun tautan ini dapat menjawab pertanyaan, lebih baik untuk memasukkan bagian-bagian penting dari jawaban di sini dan memberikan tautan untuk referensi. Jawaban hanya tautan dapat menjadi tidak valid jika halaman tertaut berubah. - Dari Ulasan
timgeb
2
Kamu benar. Saya telah memperbarui komentar saya ... Saya harap sekarang lebih tepat.
gabrielperales