„Jednostronicowe” witryny JS i SEO

128

Obecnie istnieje wiele fajnych narzędzi do tworzenia potężnych „pojedynczych stron” witryn JavaScript. Moim zdaniem jest to robione dobrze, pozwalając serwerowi działać jako API (i nic więcej) i pozwalając klientowi obsługiwać wszystkie rzeczy związane z generowaniem HTML. Problem z tym „wzorcem” to brak wsparcia dla wyszukiwarek. Przychodzą mi do głowy dwa rozwiązania:

  1. Gdy użytkownik wejdzie na stronę internetową, pozwól serwerowi renderować stronę dokładnie tak, jak klient podczas nawigacji. Więc jeśli przejdę http://example.com/my_pathbezpośrednio do serwera, wyrenderuje to samo, co klient, jeśli przejdę /my_pathprzez pushState.
  2. Niech serwer zapewni specjalną stronę internetową tylko dla robotów wyszukiwarek. Jeśli zwykły użytkownik odwiedza http://example.com/my_pathwitrynę, serwer powinien udostępnić mu wersję strony z ciężkim JavaScriptem. Ale jeśli odwiedza robota Google, serwer powinien przekazać mu minimalny kod HTML z treścią, którą Google ma zaindeksować.

Pierwsze rozwiązanie jest omówiony tutaj . Pracowałem nad stroną internetową, ale nie jest to zbyt przyjemne doświadczenie. To nie jest DRY iw moim przypadku musiałem użyć dwóch różnych silników szablonów dla klienta i serwera.

Wydaje mi się, że widziałem drugie rozwiązanie dla starych dobrych stron Flash. Takie podejście podoba mi się znacznie bardziej niż pierwsze, a przy odpowiednim narzędziu na serwerze można to zrobić bezboleśnie.

Tak więc naprawdę zastanawiam się, co następuje:

  • Czy możesz wymyślić lepsze rozwiązanie?
  • Jakie są wady drugiego rozwiązania? Jeśli Google w jakiś sposób dowie się, że nie udostępniam dokładnie tych samych treści botowi Google, co zwykły użytkownik, czy zostałbym ukarany w wynikach wyszukiwania?
user544941
źródło

Odpowiedzi:

44

Chociaż punkt 2 może być „łatwiejszy” dla Ciebie jako programisty, zapewnia jedynie indeksowanie w wyszukiwarkach. I tak, jeśli Google dowie się, że obsługujesz różne treści, możesz zostać ukarany (nie jestem w tym ekspertem, ale słyszałem, że tak się dzieje).

Zarówno SEO, jak i dostępność (nie tylko dla osoby niepełnosprawnej, ale także dostępność za pośrednictwem urządzeń mobilnych, urządzeń z ekranem dotykowym i innych niestandardowych platform komputerowych / internetowych) mają podobną filozofię: bogate semantycznie znaczniki, które są „dostępne” (tj. być dostępne, przeglądane, czytane, przetwarzane lub w inny sposób wykorzystywane) do wszystkich tych różnych przeglądarek. Czytnik ekranu, robot wyszukiwarki lub użytkownik z włączoną obsługą JavaScript powinni być w stanie bez problemu korzystać z / indeksować / rozumieć podstawowe funkcje witryny.

pushStatenie dodaje do tego ciężaru, z mojego doświadczenia. Dopiero to, co kiedyś było refleksją i „jeśli mamy czas”, znajduje się na czele tworzenia stron internetowych.

To, co opisujesz w opcji nr 1, jest zwykle najlepszym rozwiązaniem - ale, podobnie jak inne problemy z dostępnością i SEO, zrobienie tego pushStatew aplikacji z dużą ilością JavaScript wymaga planowania z góry lub stanie się znacznym obciążeniem. Powinien on być od początku zapieczętowany w architekturze strony i aplikacji - modernizacja jest bolesna i spowoduje więcej powielania niż jest to konieczne.

pushStateOstatnio pracowałem z SEO dla kilku różnych aplikacji i znalazłem to, co uważam za dobre podejście. Zasadniczo podąża za Twoim elementem nr 1, ale uwzględnia brak powielania html / templates.

Większość informacji można znaleźć w tych dwóch postach na blogu:

http://lostechies.com/derickbailey/2011/09/06/test-driving-backbone-views-with-jquery-templates-the-jasmine-gem-and-jasmine-jquery/

i

http://lostechies.com/derickbailey/2011/06/22/rendering-a-rails-partial-as-a-jquery-template/

Istota tego polega na tym, że używam szablonów ERB lub HAML (z uruchomionym Ruby on Rails, Sinatra itp.) Do renderowania po stronie serwera i do tworzenia szablonów po stronie klienta, których może używać Backbone, a także do specyfikacji Jasmine JavaScript. Eliminuje to powielanie znaczników między stroną serwera i klienta.

Następnie musisz wykonać kilka dodatkowych kroków, aby JavaScript działał z kodem HTML renderowanym przez serwer - prawdziwe ulepszenie progresywne; biorąc dostarczony znacznik semantyczny i ulepszając go o JavaScript.

Na przykład tworzę aplikację galerii obrazów z pushState. Jeśli zażądasz /images/1od serwera, wyrenderuje całą galerię obrazów na serwerze i wyśle ​​cały kod HTML, CSS i JavaScript do przeglądarki. Jeśli masz wyłączony JavaScript, będzie działać idealnie. Każda czynność, którą podejmiesz, zażąda innego adresu URL z serwera, a serwer wyrenderuje wszystkie znaczniki dla Twojej przeglądarki. Jeśli jednak masz włączony JavaScript, JavaScript pobierze już wyrenderowany kod HTML wraz z kilkoma zmiennymi wygenerowanymi przez serwer i przejmie stamtąd.

Oto przykład:

<form id="foo">
  Name: <input id="name"><button id="say">Say My Name!</button>
</form>

Po wyrenderowaniu tego przez serwer, JavaScript powinien to odebrać (używając widoku Backbone.js w tym przykładzie)

FooView = Backbone.View.extend({
  events: {
    "change #name": "setName",
    "click #say": "sayName"
  },

  setName: function(e){
    var name = $(e.currentTarget).val();
    this.model.set({name: name});
  },

  sayName: function(e){
    e.preventDefault();
    var name = this.model.get("name");
    alert("Hello " + name);
  },

  render: function(){
    // do some rendering here, for when this is just running JavaScript
  }
});

$(function(){
  var model = new MyModel();
  var view = new FooView({
    model: model,
    el: $("#foo")
  });
});

To bardzo prosty przykład, ale myślę, że ma sens.

Kiedy instantuję widok po załadowaniu strony, udostępniam istniejącą zawartość formularza, który został wyrenderowany przez serwer, do instancji widoku jako eldla widoku. Nie wywołuję renderowania ani nie generuję widoku el, gdy ładowany jest pierwszy widok. Mam dostępną metodę renderowania po uruchomieniu widoku, a cała strona jest w JavaScript. To pozwoli mi później wyrenderować widok, jeśli zajdzie taka potrzeba.

Kliknięcie przycisku „Say My Name” z włączoną obsługą JavaScript spowoduje wyświetlenie okna alertu. Bez JavaScript wysłałby z powrotem na serwer, a serwer mógłby renderować nazwę w jakimś elemencie html.

Edytować

Rozważ bardziej złożony przykład, w którym masz listę, którą należy załączyć (z komentarzy poniżej)

Powiedzmy, że masz listę użytkowników w <ul>tagu. Ta lista została wyrenderowana przez serwer, gdy przeglądarka wysłała żądanie, a wynik wygląda mniej więcej tak:

<ul id="user-list">
  <li data-id="1">Bob
  <li data-id="2">Mary
  <li data-id="3">Frank
  <li data-id="4">Jane
</ul>

Teraz musisz przejrzeć tę listę i dołączyć widok i model szkieletu do każdego <li>elementu. Za pomocą data-idatrybutu można łatwo znaleźć model, z którego pochodzi każdy tag. Będziesz wtedy potrzebował widoku kolekcji i widoku elementów, który jest wystarczająco inteligentny, aby dołączyć się do tego kodu HTML.

UserListView = Backbone.View.extend({
  attach: function(){
    this.el = $("#user-list");
    this.$("li").each(function(index){
      var userEl = $(this);
      var id = userEl.attr("data-id");
      var user = this.collection.get(id);
      new UserView({
        model: user,
        el: userEl
      });
    });
  }
});

UserView = Backbone.View.extend({
  initialize: function(){
    this.model.bind("change:name", this.updateName, this);
  },

  updateName: function(model, val){
    this.el.text(val);
  }
});

var userData = {...};
var userList = new UserCollection(userData);
var userListView = new UserListView({collection: userList});
userListView.attach();

W tym przykładzie UserListViewpętla przejdzie przez wszystkie <li>znaczniki i dołączy obiekt widoku z odpowiednim modelem dla każdego z nich. konfiguruje procedurę obsługi zdarzenia dla zdarzenia zmiany nazwy modelu i aktualizuje wyświetlany tekst elementu, gdy następuje zmiana.


Ten rodzaj procesu, polegający na przejęciu kodu HTML renderowanego przez serwer i przejęciu go przez JavaScript, jest świetnym sposobem na rozpoczęcie pracy z SEO, pushStateułatwieniami dostępu i pomocą techniczną.

Mam nadzieję, że to pomoże.

Derick Bailey
źródło
Rozumiem, ale interesujące jest to, jak renderowanie odbywa się po „przejęciu przez JavaScript”. W bardziej skomplikowanym przykładzie może być konieczne użycie nieskompilowanego szablonu na kliencie, przeglądając tablicę użytkowników w pętli, aby zbudować listę. Widok jest ponownie renderowany za każdym razem, gdy zmienia się model użytkownika. Jak byś to zrobił bez powielania szablonów (i bez proszenia serwera o renderowanie widoku dla klienta)?
user544941
2 posty na blogu, do których dołączyłem, powinny łącznie pokazać, jak tworzyć szablony, których można używać na kliencie i serwerze - bez konieczności powielania. serwer będzie musiał wyrenderować całą stronę, jeśli chcesz, aby była dostępna i przyjazna dla SEO. zaktualizowałem swoją odpowiedź, aby zawierała bardziej złożony przykład dołączania do listy użytkowników, która została wyrenderowana przez serwer
Derick Bailey
22

Myślę, że potrzebujesz tego: http://code.google.com/web/ajaxcrawling/

Możesz także zainstalować specjalny backend, który "renderuje" twoją stronę poprzez uruchomienie javascript na serwerze, a następnie przekazuje go do Google.

Połącz obie rzeczy, a otrzymasz rozwiązanie bez programowania rzeczy dwa razy. (O ile aplikacją można w pełni sterować za pomocą fragmentów kotwicy).

Ariel
źródło
Właściwie to nie jest to, czego szukam. To kilka wariantów pierwszego rozwiązania i jak wspomniałem nie jestem z niego zadowolony.
user544941
2
Nie przeczytałeś całej mojej odpowiedzi. Używasz również specjalnego zaplecza, które renderuje dla Ciebie javascript - nie piszesz rzeczy dwa razy.
Ariel
Tak, czytałem to. Ale gdybym cię dobrze zrozumiał, byłby to piekielny program, ponieważ musiałby symulować każdą akcję, która wyzwala pushState. Ewentualnie mógłbym dać mu działania bezpośrednio, ale wtedy nie jesteśmy już tacy SUCHY.
user544941
2
Myślę, że to w zasadzie przeglądarka bez frontu. Ale tak, musisz całkowicie sterować programem za pomocą fragmentów kotwicy. Musisz także upewnić się, że wszystkie linki mają w sobie odpowiedni fragment, wraz z lub zamiast onClicks.
Ariel
17

Wydaje się więc, że głównym problemem jest bycie SUCHYM

  • Jeśli używasz pushState, Twój serwer powinien wysłać ten sam dokładny kod dla wszystkich adresów URL (które nie zawierają rozszerzenia pliku do obsługi obrazów itp.) „/ Mydir / myfile”, „/ myotherdir / myotherfile” lub root ”/ „- wszystkie żądania otrzymują dokładnie ten sam kod. Musisz mieć jakiś silnik przepisywania adresu URL. Możesz również podać niewielką część kodu HTML, a reszta może pochodzić z Twojej sieci CDN (za pomocą require.js do zarządzania zależnościami - zobacz https://stackoverflow.com/a/13813102/1595913 ).
  • (sprawdź poprawność linku, konwertując link na swój schemat adresu URL i sprawdzając, czy istnieje treść, wysyłając zapytanie do statycznego lub dynamicznego źródła. Jeśli nie jest poprawne, wyślij odpowiedź 404).
  • Jeśli żądanie nie pochodzi od bota Google, wystarczy przetworzyć je normalnie.
  • Jeśli żądanie pochodzi od bota Google, używasz phantom.js - bezgłowej przeglądarki webkit ( Bezgłowa przeglądarka to po prostu w pełni funkcjonalna przeglądarka internetowa bez interfejsu graficznego” ) do renderowania html i javascript na serwerze i wysyłania google bot wynikowy html. Gdy bot analizuje kod HTML, może trafić na inne linki / stronę „pushState” na serwerze <a href="https://stackoverflow.com/someotherpage">mylink</a>, serwer przepisuje adres URL do pliku aplikacji, ładuje go w phantom.js, a wynikowy kod HTML jest wysyłany do bota i tak dalej. ..
  • Zakładam, że w Twoim html używasz zwykłych linków z jakimś rodzajem porwania (np. Używając z backbone.js https://stackoverflow.com/a/9331734/1595913 )
  • Aby uniknąć nieporozumień z linkami, oddziel kod API obsługujący json w osobnej subdomenie, np. Api.mysite.com
  • Aby poprawić wydajność, możesz wstępnie przetwarzać strony witryny pod kątem wyszukiwarek z wyprzedzeniem poza godzinami pracy, tworząc statyczne wersje stron przy użyciu tego samego mechanizmu co phantom.js, a następnie wyświetlać strony statyczne robotom Google. Wstępne przetwarzanie można wykonać za pomocą prostej aplikacji, która może analizować <a>tagi. W tym przypadku obsługa 404 jest łatwiejsza, ponieważ można po prostu sprawdzić, czy istnieje plik statyczny o nazwie zawierającej ścieżkę url.
  • Jeśli użyjesz #! Składnia hash bang dla linków do Twojej witryny ma zastosowanie podobny scenariusz, z tą różnicą, że silnik serwera przepisywania adresu URL wyszukałby _escaped_fragment_ w adresie URL i sformatowałby go zgodnie ze schematem adresu URL.
  • Istnieje kilka integracji node.js z phantom.js na github i możesz użyć node.js jako serwera WWW do generowania danych wyjściowych HTML.

Oto kilka przykładów użycia phantom.js w SEO:

http://backbonetutorials.com/seo-for-single-page-apps/

http://thedigitalself.com/blog/seo-and-javascript-with-phantomjs-server-side-rendering

Leonidaz
źródło
4

Jeśli używasz Railsów, wypróbuj poirot . To klejnot, który sprawia, że ​​ponowne użycie szablonów wąsów lub kierownic po stronie klienta i serwera jest bardzo proste .

Utwórz plik w swoich widokach, takich jak _some_thingy.html.mustache.

Renderuj po stronie serwera:

<%= render :partial => 'some_thingy', object: my_model %>

Umieść szablon głowy do użytku po stronie klienta:

<%= template_include_tag 'some_thingy' %>

Rendre po stronie klienta:

html = poirot.someThingy(my_model)
Tim Scott
źródło
3

Aby spojrzeć z nieco innego punktu widzenia, twoje drugie rozwiązanie byłoby właściwe pod względem dostępności ... Zapewniłbyś alternatywną zawartość użytkownikom, którzy nie mogą używać javascript (ci z czytnikami ekranu itp.).

To automatycznie dodałoby korzyści z SEO i, moim zdaniem, nie byłoby postrzegane jako „niegrzeczna” technika przez Google.

Clive
źródło
Czy ktoś udowodnił, że się mylisz? Minęło trochę czasu od opublikowania komentarza
jkulak
1

Ciekawy. Szukałem realnych rozwiązań, ale wydaje się to być dość problematyczne.

Właściwie bardziej skłaniałem się w kierunku twojego drugiego podejścia:

Niech serwer zapewni specjalną stronę internetową tylko dla robotów wyszukiwarek. Jeśli zwykły użytkownik odwiedzi http://example.com/my_path, serwer powinien udostępnić mu wersję witryny z rozbudowaną obsługą JavaScript. Ale jeśli odwiedza robota Google, serwer powinien przekazać mu minimalny kod HTML z treścią, którą Google ma zaindeksować.

Oto moje podejście do rozwiązania problemu. Chociaż nie potwierdzono, że działa, może dostarczyć pewnych spostrzeżeń lub pomysłów innym programistom.

Załóżmy, że używasz frameworka JS, który obsługuje funkcję „push state”, a twoja struktura zaplecza to Ruby on Rails. Masz prostą witrynę z blogiem i chciałbyś, aby wyszukiwarki indeksowały wszystkie Twoje artykuły indexi showstrony.

Powiedzmy, że masz skonfigurowane trasy w ten sposób:

resources :articles
match "*path", "main#index"

Upewnij się, że każdy kontroler po stronie serwera renderuje ten sam szablon, którego wymaga Twoja struktura po stronie klienta (html / css / javascript / etc). Jeśli żaden z kontrolerów nie jest dopasowany w żądaniu (w tym przykładzie mamy tylko zestaw akcji RESTful dla ArticlesController), po prostu dopasuj cokolwiek innego i po prostu wyrenderuj szablon i pozwól strukturze po stronie klienta obsłużyć routing. Jedyną różnicą między uderzeniem w kontroler a trafieniem w dopasowywanie symboli wieloznacznych byłaby możliwość renderowania treści na podstawie adresu URL żądanego na urządzeniach z wyłączoną obsługą JavaScript.

Z tego, co rozumiem, renderowanie treści, które nie są widoczne dla przeglądarek, jest złym pomysłem. Więc kiedy Google go indeksuje, ludzie przechodzą przez Google, aby odwiedzić daną stronę i nie ma żadnej treści, wtedy prawdopodobnie zostaniesz ukarany. Przychodzi mi na myśl, że renderujesz zawartość w divwęźle, który wykonujesz display: nonew CSS.

Jestem jednak pewien, że nie ma to znaczenia, jeśli po prostu zrobisz to:

<div id="no-js">
  <h1><%= @article.title %></h1>
  <p><%= @article.description %></p>
  <p><%= @article.content %></p>
</div>

A następnie używając JavaScript, który nie jest uruchamiany, gdy urządzenie z wyłączoną obsługą JavaScript otwiera stronę:

$("#no-js").remove() # jQuery

W ten sposób dla Google i dla każdego, kto ma urządzenia z wyłączoną obsługą JavaScript, zobaczą surową / statyczną zawartość. Tak więc zawartość jest tam fizycznie i jest widoczna dla każdego, kto ma urządzenia z wyłączoną obsługą JavaScript.

Ale gdy użytkownik odwiedza tę samą stronę i faktycznie ma włączoną obsługę JavaScript, #no-jswęzeł zostanie usunięty, aby nie zaśmiecać aplikacji. Następnie struktura po stronie klienta obsłuży żądanie przez swój router i wyświetli to, co powinien zobaczyć użytkownik, gdy włączony jest JavaScript.

Myślę, że może to być poprawna i dość łatwa w użyciu technika. Chociaż może to zależeć od złożoności Twojej witryny / aplikacji.

Chociaż, proszę, popraw mnie, jeśli tak nie jest. Pomyślałem, że podzielę się swoimi przemyśleniami.

Michael van Rooijen
źródło
1
Cóż, jeśli najpierw wyświetlisz treść, a nieco później ją usuniesz, to najprawdopodobniej użytkownik końcowy może zauważyć, że zawartość miga / migocze w przeglądarce :) Szczególnie jeśli jest to powolna przeglądarka, ogromny rozmiar treści HTML, którą próbujesz wyświetlić / usunąć opóźnienie przed załadowaniem i wykonaniem kodu JS. Co myślisz?
Evereq
1

Używaj NodeJS po stronie serwera, przeglądaj kod po stronie klienta i kieruj uri każdego żądania http (z wyjątkiem statycznych zasobów http) przez klienta po stronie serwera, aby zapewnić pierwsze `` bootnap '' (migawkę strony, w której jest). Użyj czegoś takiego jak jsdom do obsługi jquery dom-ops na serwerze. Po zwróceniu sygnału startowego skonfiguruj połączenie WebSocket. Prawdopodobnie najlepiej jest odróżnić klienta sieci Web od klienta po stronie serwera, tworząc po stronie klienta pewnego rodzaju połączenie typu wrapper (klient po stronie serwera może bezpośrednio komunikować się z serwerem). Pracowałem nad czymś takim: https://github.com/jvanveen/rnet/

Phrearch
źródło
0

Użyj szablonu zamknięcia Google do renderowania stron. Kompiluje się do javascript lub java, więc łatwo jest renderować stronę po stronie klienta lub serwera. Przy pierwszym spotkaniu z każdym klientem wyrenderuj kod HTML i dodaj javascript jako link w nagłówku. Robot odczyta tylko kod HTML, ale przeglądarka wykona skrypt. Wszystkie kolejne żądania z przeglądarki można kierować do interfejsu API, aby zminimalizować ruch.

Aleš Kotnik
źródło
0

To może ci pomóc: https://github.com/sharjeel619/SPA-SEO

Logika

  • Przeglądarka żąda od serwera aplikacji jednostronicowej, która zostanie załadowana z pojedynczego pliku index.html.
  • Programujesz kod serwera pośredniczącego, który przechwytuje żądanie klienta i rozróżnia, czy żądanie pochodzi z przeglądarki, czy z jakiegoś robota robota społecznościowego.
  • Jeśli żądanie pochodzi od jakiegoś bota robota, wykonaj wywołanie interfejsu API do serwera zaplecza, zbierz potrzebne dane, wypełnij te dane metatagami html i zwróć te tagi w formacie ciągów z powrotem do klienta.
  • Jeśli żądanie nie pochodzi od jakiegoś bota robota, po prostu zwróć plik index.html z folderu build lub dist Twojej aplikacji jednostronicowej.
Sherry Malik
źródło