Eu vejo dois problemas com o aplicativo AngularJS em relação aos mecanismos de pesquisa e SEO:
1) O que acontece com as tags personalizadas? Os mecanismos de pesquisa ignoram todo o conteúdo dessas tags? ou seja, suponho que eu tenha
<custom>
<h1>Hey, this title is important</h1>
</custom>
o <h1>
seria indexado apesar de estar dentro de tags personalizadas?
2) Existe uma maneira de evitar os motores de busca de indexação {{}} binds literalmente? isto é.
<h2>{{title}}</h2>
Eu sei que eu poderia fazer algo parecido
<h2 ng-bind="title"></h2>
mas e se eu quiser realmente deixar o rastreador "ver" o título? A renderização do lado do servidor é a única solução?
Atualização de maio de 2014
Google crawlers agora executa javascript - você pode usar o Google Webmaster Tools para entender melhor como seus sites são processados pelo Google.
resposta original
Se você deseja otimizar seu aplicativo para mecanismos de pesquisa, infelizmente não há como distribuir uma versão pré-renderizada para o rastreador. Você pode ler mais sobre as recomendações do Google para sites pesados ajax e javascript aqui .
Se esta é uma opção, eu recomendo ler este artigo sobre como fazer SEO para Angular com a renderização do lado do servidor.
Não tenho certeza do que o rastreador faz quando encontra tags personalizadas.
A maneira atual (2015) de fazer isso é usar o método pushState do JavaScript.
O PushState altera o URL na barra superior do navegador sem recarregar a página. Digamos que você tenha uma página contendo guias. As abas escondem e mostram conteúdo, e o conteúdo é inserido dinamicamente, usando AJAX ou simplesmente configurando display: none e display: block para esconder e mostrar o conteúdo correto da aba.
Quando as guias forem clicadas, use o pushState para atualizar o URL na barra de endereço. Quando a página é renderizada, use o valor na barra de endereços para determinar qual guia exibir. Angular roteamento fará isso para você automaticamente.
Há duas maneiras de acessar um aplicativo de página única (SPA) do PushState
O hit inicial no site envolverá o acesso direto ao URL. Os acessos subseqüentes serão simplesmente AJAX no conteúdo, pois o PushState atualiza o URL.
Os rastreadores coletam links de uma página e os adicionam a uma fila para processamento posterior. Isso significa que, para um rastreador, todos os hits no servidor são um impacto direto, eles não navegam pelo Pushstate.
A pré-composição agrupa a carga inicial na primeira resposta do servidor, possivelmente como um objeto JSON. Isso permite que o mecanismo de pesquisa renderize a página sem executar a chamada AJAX.
Há algumas evidências que sugerem que o Google pode não executar solicitações AJAX. Mais sobre isso aqui:
O Google tem sido capaz de analisar JavaScript por algum tempo agora, e é por isso que eles originalmente desenvolveram o Chrome, para atuar como um navegador sem cabeçalho completo para a aranha do Google. Se um link tiver um atributo href válido, o novo URL poderá ser indexado. Não há mais nada a fazer.
Se clicar em um link, além disso, aciona uma chamada pushState, o site pode ser navegado pelo usuário via PushState.
Atualmente, o PushState é suportado pelo Google e pelo Bing.
Aqui está Matt Cutts respondendo à pergunta de Paul Irish sobre o PushState para SEO:
Aqui está o Google anunciando o suporte total do JavaScript para a aranha:
http://googlewebmastercentral.blogspot.de/2014/05/understanding-web-pages-better.html
O resultado é que o Google suporta o PushState e indexará URLs do PushState.
Veja também as ferramentas do Google para webmasters como "Googlebot". Você verá seu JavaScript (incluindo o Angular) executado.
Aqui está o anúncio do Bing sobre suporte para URLs do PushState datados de março de 2013:
http://blogs.bing.com/webmaster/2013/03/21/search-engine-optimization-best-practices-for-ajax-urls/
Os URLs do Hashbang eram um substituto feio que exigia que o desenvolvedor fornecesse uma versão pré-renderizada do site em um local especial. Eles ainda funcionam, mas você não precisa usá-los.
URLs de hashbang são assim:
domain.com/#!path/to/resource
Isso seria emparelhado com uma metatag como esta:
<meta name="fragment" content="!">
O Google não os indexará neste formulário, mas, em vez disso, extrairá uma versão estática do site do URL _escaped_fragments_ e o indexará.
Os URLs pushstate parecem com qualquer URL comum:
domain.com/path/to/resource
A diferença é que Angular lida com eles interceptando a mudança para document.location lidando com isso em JavaScript.
Se você quiser usar URLs PushState (e provavelmente você faz) retire todas as antigas URLs e metatags de estilo de hash e simplesmente ative o modo HTML5 em seu bloco de configuração.
As ferramentas do Google para webmasters agora contêm uma ferramenta que permite buscar um URL como o google e renderizar o JavaScript conforme o Google o renderiza.
https://www.google.com/webmasters/tools/googlebot-fetch
Para gerar URLs reais em Angular, em vez de # pré-fixados, defina o modo HTML5 no seu objeto $ locationProvider.
$locationProvider.html5Mode(true);
Como você está usando URLs reais, você precisará garantir que o mesmo modelo (mais algum conteúdo pré-configurado) seja enviado pelo seu servidor para todos os URLs válidos. Como você faz isso irá variar dependendo da arquitetura do seu servidor.
Seu aplicativo pode usar formas incomuns de navegação, por exemplo, passar o mouse ou rolar. Para garantir que o Google possa direcionar seu aplicativo, eu provavelmente sugeriria criar um sitemap, uma lista simples de todos os URLs que seu aplicativo responde. Você pode colocar isso no local padrão (/ sitemap ou /sitemap.xml) ou informar ao Google sobre isso usando as ferramentas do webmaster.
É uma boa ideia ter um sitemap de qualquer maneira.
Pushstate funciona no IE10. Em navegadores mais antigos, Angular retornará automaticamente para URLs de estilo hash
O conteúdo a seguir é renderizado usando um URL de pushstate com pré-composição:
http://html5.gingerhost.com/london
Como pode ser verificado, em este link , o conteúdo é indexado e está aparecendo no Google.
Como o mecanismo de pesquisa sempre atingirá o servidor para cada solicitação, você poderá exibir códigos de status do cabeçalho do servidor e esperar que o Google os veja.
Google, Yahoo, Bing e outros mecanismos de pesquisa rastreiam a Web de maneiras tradicionais usando rastreadores tradicionais. Eles rodam robôsque rastreiam o HTML em páginas da web, coletando informações ao longo do caminho. Eles mantêm palavras interessantes e buscam outros links para outras páginas (esses links, a quantidade deles e o número deles entram em jogo com SEO).
A resposta tem a ver com o fato de que os robôs do mecanismo de pesquisa funcionam em navegadores sem cabeçalho e na maioria das vezes fazem nãopossuem um mecanismo de renderização de javascript para renderizar o javascript de uma página. Isso funciona para a maioria das páginas não se preocupam com JavaScript renderizando sua página, já que seu conteúdo já está disponível.
Felizmente, os rastreadores dos sites maiores começaram a implementar um mecanismo que nos permite tornar os sites JavaScript rastreáveis, mas exige que implementemos uma alteração em nosso site .
Se alterarmos a variável hashPrefix
para #!
em vez de simplesmente #
, os mecanismos de pesquisa modernos alterarão a solicitação para usar _escaped_fragment_
em vez de #!
. (Com o modo HTML5, ou seja, onde temos links sem o prefixo hash, podemos implementar esse mesmo recurso observando o cabeçalho User Agent
em nosso backend).
Isto é, em vez de um pedido de um navegador normal que se parece com:
http://www.ng-newsletter.com/#!/signup/page
Um mecanismo de pesquisa pesquisará a página com:
http://www.ng-newsletter.com/?_escaped_fragment_=/signup/page
Podemos definir o prefixo hash de nossos aplicativos Angular usando um método interno de ngRoute
:
angular.module('myApp', [])
.config(['$location', function($location) {
$location.hashPrefix('!');
}]);
E, se estivermos usando html5Mode
, precisaremos implementar isso usando a meta tag:
<meta name="fragment" content="!">
Lembrete, podemos definir a html5Mode()
com o serviço $location
:
angular.module('myApp', [])
.config(['$location',
function($location) {
$location.html5Mode(true);
}]);
Temos muitas oportunidades para determinar como lidaremos com a entrega de conteúdo aos mecanismos de pesquisa como HTML estático. Podemos hospedar um backend nós mesmos, podemos usar um serviço para hospedar um back-end para nós, podemos usar um proxy para entregar o conteúdo, etc. Vamos dar uma olhada em algumas opções:
Podemos criar um serviço para lidar com o rastreamento de nosso próprio site usando um navegador sem cabeçalho, como phantomjs ou zombiejs, tirando um instantâneo da página com dados renderizados e armazenando-os como HTML. Sempre que vemos a string de consulta ?_escaped_fragment_
em uma solicitação de pesquisa, podemos entregar o instantâneo HTML estático que tiramos da página em vez da página pré-renderizada por meio de apenas JS. Isso exige que tenhamos um back-end que entregue nossas páginas com lógica condicional no meio. Podemos usar algo como prerender.io backend como ponto de partida para executar isso sozinhos. É claro que ainda precisamos lidar com o proxy e o processamento de snippets, mas é um bom começo.
A maneira mais fácil e rápida de obter conteúdo no mecanismo de pesquisa é usar um serviço brombone , seo.js , seo4ajax e prerender.io são bons exemplos desses que hospedará a renderização de conteúdo acima para você. Essa é uma boa opção para os momentos em que não queremos lidar com a execução de um servidor/proxy. Além disso, geralmente é super rápido.
Para mais informações sobre Angular e SEO, escrevemos um extenso tutorial sobre isso em http://www.ng-newsletter.com/posts/serious-angular-seo.html e detalhamos ainda mais em nosso livro ng-book: O livro completo sobre AngularJS . Confira em ng-book.com .
Você deve realmente verificar o tutorial sobre a construção de um site AngularJS SEO-friendly no ano do blog moo. Ele percorre todos os passos descritos na documentação do Angular. http://www.yearofmoo.com/2012/11/angularjs-and-seo.html
Usando essa técnica, o mecanismo de pesquisa vê o HTML expandido em vez das tags personalizadas.
Isso mudou drasticamente.
Se você usar: $ locationProvider.html5Mode (true); você está definido.
Não há mais páginas de renderização.
As coisas mudaram um pouco desde que esta pergunta foi feita. Agora existem opções para permitir que o Google indexe seu site AngularJS. A opção mais fácil que encontrei foi usarhttp://prerender.iofree service que irá gerar as páginas que podem ser movidas para você e servir para os mecanismos de busca. É suportado em quase todas as plataformas web do lado do servidor. Eu comecei recentemente a usá-los e o suporte também é excelente.
Eu não tenho nenhuma afiliação com eles, isso vem de um usuário feliz.
O site da própria Angular serve conteúdo simplificado para os mecanismos de busca: http://docs.angularjs.org/?_escaped_fragment_=/tutorial/step_09
Digamos que seu app Angular esteja consumindo uma API JSON com Node.js/Express, como /api/path/to/resource
. Talvez você possa redirecionar qualquer solicitação com ?_escaped_fragment_
para /api/path/to/resource.html
e usar negotiation de conteúdo para renderizar um modelo HTML do conteúdo, em vez de retornar os dados JSON.
A única coisa é que as rotas Angular precisariam corresponder a 1: 1 com sua API REST.
EDIT: Estou percebendo que isso tem o potencial de realmente confundir sua REST API e eu não recomendo fazê-lo fora de casos de uso muito simples, onde poderia seja um ajuste natural.
Em vez disso, você pode usar um conjunto totalmente diferente de rotas e controladores para o seu conteúdo amigo do robô. Mas então você está duplicando todas as suas rotas e controladores AngularJS no Node/Express.
Eu decidi gerar snapshots com um navegador sem cabeçalho, embora eu ache que é um pouco menos que ideal.
Uma boa prática pode ser encontrada aqui:
http://scotch.io/tutorials/javascript/angularjs-seo-with-prerender-io?_escaped_fragment_=tag
A partir de agora, o Google mudou sua proposta de rastreamentoAJAX.
tl; dr: [Google] não está mais recomendando a AJAX proposta de rastreamento [Google] feita em 2009.
A Rastreio Ajax Rastreável do Google, conforme mencionado nas outras respostas aqui, é basicamente a resposta.
Se você estiver interessado em saber como outros mecanismos de pesquisa e bots sociais lidam com os mesmos problemas, escrevi o estado da arte aqui: http://blog.ajaxsnapshots.com/2013/11/googles-crawlable-ajax-specification .html
Eu trabalho para um https://ajaxsnapshots.com , uma empresa que implementa o Crawlable Ajax Spec como um serviço - as informações desse relatório são baseadas em observações de nossos registros.
Eu encontrei uma solução elegante que cobriria a maioria de suas bases. Eu escrevi sobre isso inicialmente aqui e respondi a outra pergunta semelhante StackOverflow aqui que faz referência a ele.
Além disso, esta solução também inclui tags de fallback codificados, caso o Javascript não seja selecionado pelo rastreador. Eu não o delineei explicitamente, mas vale a pena mencionar que você deve ativar o modo HTML5 para obter suporte de URL adequado.
Observe também: estes não são os arquivos completos, apenas as partes importantes daquelas que são relevantes. Se você precisar de ajuda para escrever o texto padrão para diretivas, serviços, etc., que podem ser encontrados em outro lugar. Enfim, aqui vai ...
app.js
É aqui que você fornece os metadados personalizados para cada uma das suas rotas (título, descrição etc.)
$routeProvider
.when('/', {
templateUrl: 'views/homepage.html',
controller: 'HomepageCtrl',
metadata: {
title: 'The Base Page Title',
description: 'The Base Page Description' }
})
.when('/about', {
templateUrl: 'views/about.html',
controller: 'AboutCtrl',
metadata: {
title: 'The About Page Title',
description: 'The About Page Description' }
})
metadata-service.js (serviço)
Define as opções de metadados personalizados ou usa padrões como fallbacks.
var self = this;
// Set custom options or use provided fallback (default) options
self.loadMetadata = function(metadata) {
self.title = document.title = metadata.title || 'Fallback Title';
self.description = metadata.description || 'Fallback Description';
self.url = metadata.url || $location.absUrl();
self.image = metadata.image || 'fallbackimage.jpg';
self.ogpType = metadata.ogpType || 'website';
self.twitterCard = metadata.twitterCard || 'summary_large_image';
self.twitterSite = metadata.twitterSite || '@fallback_handle';
};
// Route change handler, sets the route's defined metadata
$rootScope.$on('$routeChangeSuccess', function (event, newRoute) {
self.loadMetadata(newRoute.metadata);
});
metaproperty.js (diretiva)
Empacota os resultados do serviço de metadados para a visualização.
return {
restrict: 'A',
scope: {
metaproperty: '@'
},
link: function postLink(scope, element, attrs) {
scope.default = element.attr('content');
scope.metadata = metadataService;
// Watch for metadata changes and set content
scope.$watch('metadata', function (newVal, oldVal) {
setContent(newVal);
}, true);
// Set the content attribute with new metadataService value or back to the default
function setContent(metadata) {
var content = metadata[scope.metaproperty] || scope.default;
element.attr('content', content);
}
setContent(scope.metadata);
}
};
index.html
Termine com as tags de fallback codificadas mencionadas anteriormente, para rastreadores que não podem pegar qualquer JavaScript.
<head>
<title>Fallback Title</title>
<meta name="description" metaproperty="description" content="Fallback Description">
<!-- Open Graph Protocol Tags -->
<meta property="og:url" content="fallbackurl.com" metaproperty="url">
<meta property="og:title" content="Fallback Title" metaproperty="title">
<meta property="og:description" content="Fallback Description" metaproperty="description">
<meta property="og:type" content="website" metaproperty="ogpType">
<meta property="og:image" content="fallbackimage.jpg" metaproperty="image">
<!-- Twitter Card Tags -->
<meta name="Twitter:card" content="summary_large_image" metaproperty="twitterCard">
<meta name="Twitter:title" content="Fallback Title" metaproperty="title">
<meta name="Twitter:description" content="Fallback Description" metaproperty="description">
<meta name="Twitter:site" content="@fallback_handle" metaproperty="twitterSite">
<meta name="Twitter:image:src" content="fallbackimage.jpg" metaproperty="image">
</head>
Isso deve ajudar dramaticamente na maioria dos casos de uso de mecanismos de pesquisa. Se você quiser renderização totalmente dinâmica para os rastreadores de redes sociais (que são duvidosos no suporte a Javascript), ainda será necessário usar um dos serviços de pré-renderização mencionados em algumas das outras respostas.
Espero que isto ajude!
Com Angular Universal, você pode gerar páginas de destino para o aplicativo que se parece com o aplicativo completo e, em seguida, carregar seu Angular aplicativo por trás dele.
Angular Universal gera HTML puro significa páginas não-javascript no servidor e as serve aos usuários sem atrasos. Assim, você pode lidar com qualquer rastreador, bot e usuário (que já tem baixa velocidade de CPU e rede). Então você pode redirecioná-los por links/botões para o seu aplicativo angular real que já está carregado por trás dele. Esta solução é recomendada pelo site oficial. -Mais informações sobre SEO e Angular Universal-
Use algo como PreRender, ele faz páginas estáticas do seu site para que os mecanismos de pesquisa possam indexá-lo.
Aqui você pode descobrir quais plataformas estão disponíveis: https://prerender.io/documentation/install-middleware#asp-net
Crawlers (ou bots) são projetados para rastrear o conteúdo HTML de páginas da web, mas devido a AJAX operações para obtenção de dados assíncrona, isso se tornou um problema, já que é necessário renderizar uma página e mostrar conteúdo dinâmico nela. Da mesma forma, AngularJS
também usa um modelo assíncrono, o que cria um problema para os rastreadores do Google.
Alguns desenvolvedores criam páginas html básicas com dados reais e veiculam essas páginas do lado do servidor no momento do rastreamento. Podemos renderizar as mesmas páginas com PhantomJS
no lado do servidor que tenha _escaped_fragment_
(porque o Google procura #!
nos URLs do nosso site e depois pega tudo depois do #!
e o adiciona no parâmetro de consulta _escaped_fragment_
). Para mais detalhes, por favor leia este blog .
Os rastreadores não precisam de um rico gui bem estilizado, eles só querem ver o conteúdo , então você não precisa dar a eles um instantâneo de uma página que foi construída para humanos.
Minha solução: para fornece ao rastreador o que o rastreador deseja :
Você deve pensar no que o rastreador quer e dar a ele apenas isso.
DICA não mexa nas costas. Basta adicionar um pouco de frontview do lado do servidor usando a mesma API