Cómo agregar Lunr Search a su sitio web de Gatsby

 

 

 


Índice
  1. empezando
  • ¿Invertir índice con Lunr.js?
    1. Creando un índice en gatsby-node.js
  • Mejora de páginas con el componente de formulario de búsqueda
    1. Página de resultados de búsqueda
    2. Widget de búsqueda instantánea
    3. Haciéndolo persistente
    4. Crear una consulta de búsqueda personalizada
    5. Pensamientos finales
  • La forma Jamstack de pensar y crear sitios web se está volviendo cada vez más popular.

     

    ¿Has probado ya Gatsby, Nuxt o Gridsome (por citar sólo algunos)? Lo más probable es que su primer contacto haya sido un “¡Guau!” momento: muchas cosas se configuran automáticamente y están listas para usar.

    Sin embargo, existen algunos desafíos, uno de los cuales es la funcionalidad de búsqueda. Si está trabajando en cualquier tipo de sitio basado en contenido, probablemente se encontrará con la búsqueda y cómo manejarla. ¿Se puede hacer sin ninguna tecnología externa del lado del servidor?

    La búsqueda no es una de esas cosas que vienen listas para usar con Jamstack. Se requieren algunas decisiones e implementación adicionales.

    Afortunadamente tenemos un montón de opciones que pueden adaptarse más o menos a un proyecto. Podríamos utilizar la potente API de búsqueda como servicio de Algolia. Viene con un plan gratuito que está restringido a proyectos no comerciales con capacidad limitada. Si usamos WordPress con WPGraphQL como fuente de datos, podremos aprovechar la funcionalidad de búsqueda nativa de WordPress y Apollo Client. Raymond Camden exploró recientemente algunas opciones de búsqueda de Jamstack, incluido apuntar un formulario de búsqueda directamente a Google.

     

    En este artículo, crearemos un índice de búsqueda y agregaremos funcionalidad de búsqueda a un sitio web de Gatsby con Lunr, una biblioteca JavaScript liviana que proporciona una búsqueda extensible y personalizable sin la necesidad de servicios externos del lado del servidor. Lo usamos recientemente para agregar “Buscar por nombre de tartán” a nuestro proyecto Gatsby tartanify.com. Absolutamente queríamos una funcionalidad de búsqueda persistente a medida que se escribe, lo que planteaba algunos desafíos adicionales. Pero eso es lo que lo hace interesante, ¿verdad? Discutiré algunas de las dificultades que enfrentamos y cómo las abordamos en la segunda mitad de este artículo.

    empezando

    En aras de la simplicidad, utilizamos el iniciador del blog oficial de Gatsby. El uso de un iniciador genérico nos permite abstraer muchos aspectos de la creación de un sitio web estático. Si lo estás siguiendo, asegúrate de instalarlo y ejecutarlo:

    gatsby new gatsby-starter-blog https://github.com/gatsbyjs/gatsby-starter-blogcd gatsby-starter-bloggatsby develop

    Es un blog pequeño con tres publicaciones que podemos ver abriéndolo https://localhost:8000/___graphqlen el navegador.

    ¿Invertir índice con Lunr.js?

    Lunr utiliza un índice invertido a nivel de registro como estructura de datos. El índice invertido almacena la asignación de cada palabra encontrada dentro de un sitio web a su ubicación (básicamente un conjunto de rutas de página). Depende de nosotros decidir qué campos (por ejemplo, título, contenido, descripción, etc.) proporcionar las claves (palabras) para el índice.

    Para nuestro ejemplo de blog, decidí incluir todos los títulos y el contenido de cada artículo. Tratar los títulos es sencillo ya que están compuestos únicamente de palabras. Indexar contenido es un poco más complejo. Mi primer intento fue utilizar el rawMarkdownBodycampo. Desafortunadamente, rawMarkdownBodyintroduzca algunas claves no deseadas resultantes de la sintaxis de rebajas.

    Obtuve un índice “limpio” usando el campo html junto con el paquete striptags (que, como sugiere el nombre, elimina las etiquetas HTML). Antes de entrar en detalles, veamos la documentación de Lunr.

    Así es como creamos y completamos el índice Lunr. Usaremos este fragmento en un momento, específicamente en nuestro gatsby-node.jsarchivo.

    const index = lunr(function () { this.ref('slug') this.field('title') this.field('content') for (const doc of documents) { this.add(doc) }})

    documentses una matriz de objetos, cada uno con una slugpropiedad titley content:

    { slug: '/post-slug/', title: 'Post Title', content: 'Post content with all HTML tags stripped out.'}

    Definiremos una clave de documento única (la slug) y dos campos (la titley content, o los proveedores de claves). Finalmente agregaremos todos los documentos, uno por uno.

     

    Empecemos.

    Creando un índice en gatsby-node.js

    Empecemos instalando las bibliotecas que vamos a utilizar.

    yarn add lunr graphql-type-json striptags

    A continuación, necesitamos editar el gatsby-node.jsarchivo. El código de este archivo se ejecuta una vez en el proceso de construcción de un sitio y nuestro objetivo es agregar la creación de índices a las tareas que Gatsby ejecuta durante la construcción.

    CreateResolverses una de las API de Gatsby que controla la capa de datos GraphQL. En este caso particular, lo usaremos para crear un nuevo campo raíz; Llamémoslo LunrIndex.

    El almacén de datos internos y las capacidades de consulta de Gatsby están expuestos a los solucionadores de campos GraphQL en context.nodeModel. Con getAllNodes, podemos obtener todos los nodos de un tipo específico:

    /* gatsby-node.js */const { GraphQLJSONObject } = require(`graphql-type-json`)const striptags = require(`striptags`)const lunr = require(`lunr`)exports.createResolvers = ({ cache, createResolvers }) = { createResolvers({ Query: { LunrIndex: { type: GraphQLJSONObject, resolve: (source, args, context, info) = { const blogNodes = context.nodeModel.getAllNodes({ type: `MarkdownRemark`, }) const type = info.schema.getType(`MarkdownRemark`) return createIndex(blogNodes, type, cache) }, }, }, })} 

    Ahora centrémonos en la createIndexfunción. Ahí es donde usaremos el fragmento de Lunr que mencionamos en la última sección.

    /* gatsby-node.js */const createIndex = async (blogNodes, type, cache) = { const documents = [] // Iterate over all posts for (const node of blogNodes) { const html = await type.getFields().html.resolve(node) // Once html is resolved, add a slug-title-content object to the documents array documents.push({ slug: node.fields.slug, title: node.frontmatter.title, content: striptags(html), }) } const index = lunr(function() { this.ref(`slug`) this.field(`title`) this.field(`content`) for (const doc of documents) { this.add(doc) } }) return index.toJSON()}

    ¿Has notado que en lugar de acceder al elemento HTML directamente con const html = node.html, estamos usando una awaitexpresión? Eso es porque node.htmlaún no está disponible. El complemento gatsby-transformer-remark (utilizado por nuestro iniciador para analizar archivos Markdown) no genera HTML a partir de Markdown inmediatamente al crear los MarkdownRemarknodos. En cambio, htmlse genera de forma diferida cuando se llama al solucionador de campos html en una consulta. En realidad, lo mismo se aplica a los excerptque necesitaremos dentro de un momento.

    Miremos hacia adelante y pensamos en cómo vamos a mostrar los resultados de la búsqueda. Los usuarios obtienen esperan un enlace a la publicación correspondiente, con su título como texto ancla. Es muy probable que tampoco les importen un extracto breve.

     

    La búsqueda de Lunr devuelve una matriz de objetos que representan documentos coincidentes por refpropiedad (que es la clave de documento única slugen nuestro ejemplo). Esta matriz no contiene el título del documento ni el contenido. Por lo tanto, necesitamos almacenar en algún lugar el título de la publicación y el extracto correspondiente a cada slug. Podemos hacerlo dentro de nuestro LunrIndexcomo se muestra a continuación:

    /* gatsby-node.js */const createIndex = async (blogNodes, type, cache) = { const documents = [] const store = {} for (const node of blogNodes) { const {slug} = node.fields const title = node.frontmatter.title const [html, excerpt] = await Promise.all([ type.getFields().html.resolve(node), type.getFields().excerpt.resolve(node, { pruneLength: 40 }), ]) documents.push({ // unchanged }) store[slug] = { title, excerpt, } } const index = lunr(function() { // unchanged }) return { index: index.toJSON(), store }}

    Nuestro índice de búsqueda cambia solo si se modifica una de las publicaciones o se agrega una nueva publicación. No necesitamos reconstruir el índice cada vez que ejecutamos gatsby develop. Para evitar compilaciones innecesarias, aprovechemos la API de caché:

    /* gatsby-node.js */const createIndex = async (blogNodes, type, cache) = { const cacheKey = `IndexLunr` const cached = await cache.get(cacheKey) if (cached) { return cached } // unchanged const json = { index: index.toJSON(), store } await cache.set(cacheKey, json) return json}

    Mejora de páginas con el componente de formulario de búsqueda

    Ahora podemos pasar al principio de nuestra implementación. Comencemos por crear un componente de formulario de búsqueda.

    touch src/components/search-form.js

    Opto por una solución sencilla: una entrada de type="search", junto con una etiqueta y acompañada de un botón de envío, todo dentro de una etiqueta de formulario con la searchfunción de punto de referencia.

    Agregaremos dos controladores de eventos, handleSubmital enviar el formulario y handleChangeal cambiar la entrada de búsqueda.

    /* src/components/search-form.js */import React, { useState, useRef } from "react"import { navigate } from "@reach/router"const SearchForm = ({ initialQuery = "" }) = { // Create a piece of state, and initialize it to initialQuery // query will hold the current value of the state, // and setQuery will let us change it const [query, setQuery] = useState(initialQuery) // We need to get reference to the search input element const inputEl = useRef(null) // On input change use the current value of the input field (e.target.value) // to update the state's query value const handleChange = e = { setQuery(e.target.value) } // When the form is submitted navigate to /search // with a query q paramenter equal to the value within the input search const handleSubmit = e = { e.preventDefault() // `inputEl.current` points to the mounted search input element const q = inputEl.current.value navigate(`/search?q=${q}`) } return ( form role="search" onSubmit={handleSubmit} label htmlFor="search-input" style={{ display: "block" }} Search for: /label input ref={inputEl} type="search" value={query} placeholder="e.g. duck" onChange={handleChange} / button type="submit"Go/button /form )}export default SearchForm

    ¿Has notado que estamos importando navigatedesde el @reach/routerpaquete? Esto es necesario ya que ni Gatsby Link/ni navigateproporcionan navegación en ruta con un parámetro de consulta. En su lugar, podemos importar @reach/router(no es necesario instalarlo porque Gatsby ya lo incluye) y usar su navigatefunción.

     

    Ahora que hemos creado nuestro componente, agreguémoslo a nuestra página de inicio (como se muestra a continuación) y a la página 404.

    /* src/pages/index.js */// unchangedimport SearchForm from "../components/search-form"const BlogIndex = ({ data, location }) = { // unchanged return ( Layout location={location} title={siteTitle} SEO / Bio / SearchForm / // unchanged 

    Página de resultados de búsqueda

    Nuestro SearchFormcomponente navega a la /searchruta cuando se envía el formulario, pero por el momento no hay nada detrás de esta URL. Eso significa que necesitamos agregar una nueva página:

    touch src/pages/search.js

    Procedí copiando y adaptando el contenido de la index.jspágina. Una de las modificaciones esenciales se refiere a la consulta de la página (ver al final del archivo). Reemplazaremos allMarkdownRemarkcon el LunrIndexcampo.

    /* src/pages/search.js */import React from "react"import { Link, graphql } from "gatsby"import { Index } from "lunr"import Layout from "../components/layout"import SEO from "../components/seo"import SearchForm from "../components/search-form"
// We can access the results of the page GraphQL query via the data propsconst SearchPage = ({ data, location }) = { const siteTitle = data.site.siteMetadata.title // We can read what follows the ?q= here // URLSearchParams provides a native way to get URL params // location.search.slice(1) gets rid of the "?" const params = new URLSearchParams(location.search.slice(1)) const q = params.get("q") || ""
 // LunrIndex is available via page query const { store } = data.LunrIndex // Lunr in action here const index = Index.load(data.LunrIndex.index) let results = [] try { // Search is a lunr method results = index.search(q).map(({ ref }) = { // Map search results to an array of {slug, title, excerpt} objects return { slug: ref, ...store[ref], } }) } catch (error) { console.log(error) } return ( // We will take care of this part in a moment )}export default SearchPageexport const pageQuery = graphql` query { site { siteMetadata { title } } LunrIndex }` 

    Ahora que sabemos cómo recuperar el valor de la consulta y las publicaciones coincidentes, mostremos el contenido de la página. Observe que en la página de búsqueda pasamos el valor de la consulta al SearchForm /componente a través de los initialQueryaccesorios. Cuando el usuario llega a la página de resultados de búsqueda, su consulta de búsqueda debe permanecer en el campo de entrada. Historias curiosas

     

    return ( Layout location={location} title={siteTitle} SEO / {q ? h1Search results/h1 : h1What are you looking for?/h1} SearchForm initialQuery={q} / {results.length ? ( results.map(result = { return ( article key={result.slug} h2 Link to={result.slug} {result.title || result.slug} /Link /h2 p{result.excerpt}/p /article ) }) ) : ( pNothing found./p )} /Layout)

    Puede encontrar el código completo en esta bifurcación del blog gatsby-starter y en la demostración en vivo implementada en Netlify.

    Widget de búsqueda instantánea

    Encontrar la forma más “lógica” y fácil de usar de implementar la búsqueda puede ser un desafío en sí mismo. Pasemos ahora al ejemplo de la vida real de tartanify.com, un sitio web impulsado por Gatsby que reúne más de 5000 patrones de tartán. Dado que los tartán a menudo se asocian con clanes u organizaciones, la posibilidad de buscar un tartán por nombre parece tener sentido.

    Creamos tartanify.com como un proyecto paralelo en el que nos sentimos absolutamente libres de experimentar con cosas. No queríamos una página de resultados de búsqueda clásica, sino un "widget" de búsqueda instantánea . A menudo, una determinada palabra clave de búsqueda se corresponde con una serie de resultados; por ejemplo, "Ramsay" viene en seis variaciones. Imaginamos que el widget de búsqueda sería persistente, lo que significa que debería permanecer en su lugar cuando un usuario navega de un tartán coincidente a otro.

    Déjame mostrarte cómo lo hicimos funcionar con Lunr. El primer paso para crear el índice es muy similar al ejemplo de gatsby-starter-blog, sólo que más simple:

    /* gatsby-node.js */exports.createResolvers = ({ cache, createResolvers }) = { createResolvers({ Query: { LunrIndex: { type: GraphQLJSONObject, resolve(source, args, context) { const siteNodes = context.nodeModel.getAllNodes({ type: `TartansCsv`, }) return createIndex(siteNodes, cache) }, }, }, })}const createIndex = async (nodes, cache) = { const cacheKey = `LunrIndex` const cached = await cache.get(cacheKey) if (cached) { return cached } const store = {} const index = lunr(function() { this.ref(`slug`) this.field(`title`) for (node of nodes) { const { slug } = node.fields const doc = { slug, title: node.fields.Unique_Name, } store[slug] = { title: doc.title, } this.add(doc) } }) const json = { index: index.toJSON(), store } cache.set(cacheKey, json) return json}

    Optamos por la búsqueda instantánea, lo que significa que la búsqueda se activa con cualquier cambio en la entrada de búsqueda en lugar de enviar un formulario.

    /* src/components/searchwidget.js */import React, { useState } from "react"import lunr, { Index } from "lunr"import { graphql, useStaticQuery } from "gatsby"import SearchResults from "./searchresults"
const SearchWidget = () = { const [value, setValue] = useState("") // results is now a state variable const [results, setResults] = useState([])
 // Since it's not a page component, useStaticQuery for quering data // https://www.gatsbyjs.org/docs/use-static-query/ const { LunrIndex } = useStaticQuery(graphql` query { LunrIndex } `) const index = Index.load(LunrIndex.index) const { store } = LunrIndex const handleChange = e = { const query = e.target.value setValue(query) try { const search = index.search(query).map(({ ref }) = { return { slug: ref, ...store[ref], } }) setResults(search) } catch (error) { console.log(error) } } return ( div className="search-wrapper" // You can use a form tag as well, as long as we prevent the default submit behavior div role="search" label htmlFor="search-input" className="visually-hidden" Search Tartans by Name /label input type="search" value={value} onChange={handleChange} placeholder="Search Tartans by Name" / /div SearchResults results={results} / /div )}export default SearchWidget

    Están SearchResultsestructurados así:

     

    /* src/components/searchresults.js */import React from "react"import { Link } from "gatsby"const SearchResults = ({ results }) = ( div {results.length ? ( h2{results.length} tartan(s) matched your query/h2 ul {results.map(result = ( li key={result.slug} Link to={`/tartan/${result.slug}`}{result.title}/Link /li ))} /ul / ) : ( pSorry, no matches found./p )} /div)export default SearchResults

    Haciéndolo persistente

    ¿Dónde deberíamos utilizar este componente? Podríamos agregarlo al Layoutcomponente. El problema es que nuestro formulario de búsqueda se desmontará cuando la página cambie de esa manera. Si un usuario desea explorar todos los tartanes asociados con el clan "Ramsay", deberá volver a escribir su consulta varias veces. Eso no es ideal.

    Thomas Weibenfalk ha escrito un excelente artículo sobre cómo mantener el estado entre páginas con el estado local en Gatsby.js. Usaremos la misma técnica, donde la wrapPageElementAPI del navegador establece elementos de interfaz de usuario persistentes alrededor de las páginas.

    Agreguemos el siguiente código al archivo gatsby-browser.js. Es posible que necesites agregar este archivo a la raíz de tu proyecto.

    /* gatsby-browser.js */import React from "react"import SearchWrapper from "./src/components/searchwrapper"export const wrapPageElement = ({ element, props }) = ( SearchWrapper {...props}{element}/SearchWrapper)

    Ahora agreguemos un nuevo archivo de componente:

    touch src/components/searchwrapper.js

    En lugar de agregar SearchWidgetun componente al Layout, lo agregaremos al SearchWrappery sucederá la magia. ✨

    /* src/components/searchwrapper.js */import React from "react"import SearchWidget from "./searchwidget"
const SearchWrapper = ({ children }) = ( {children} SearchWidget / /)export default SearchWrapper

    Crear una consulta de búsqueda personalizada

    En este punto, comencé a probar diferentes palabras clave, pero rápidamente me di cuenta de que la consulta de búsqueda predeterminada de Lunr podría no ser la mejor solución cuando se usa para búsqueda instantánea.

     

    ¿Por qué? Imaginemos que buscamos tartanes asociados al nombre MacCallum. Mientras escribe “MacCallum” letra por letra, esta es la evolución de los resultados:

    • m– 2 partidos (Lyon, Jeffrey M, Lyon, Jeffrey M (Caza))
    • ma- No hay coincidencias
    • mac– 1 partido (Brighton Mac Dermotte)
    • macc- No hay coincidencias
    • macca- No hay coincidencias
    • maccal– 1 partido (MacCall)
    • maccall– 1 partido (MacCall)
    • maccallu- No hay coincidencias
    • maccallum– 3 partidos (MacCallum, MacCallum #2, MacCallum de Berwick)

    Los usuarios probablemente escribirán el nombre completo y presionarán el botón si ponemos un botón a disposición. Pero con la búsqueda instantánea, es probable que un usuario la abandone antes de tiempo porque puede esperar que los resultados solo puedan limitar las letras que se agregan a la consulta de palabras clave.

    Ese no es el único problema. Esto es lo que obtenemos con "Callum":

    • c– 3 partidos no relacionados
    • ca- No hay coincidencias
    • cal- No hay coincidencias
    • call- No hay coincidencias
    • callu- No hay coincidencias
    • callum- un partido

    Puede ver el problema si alguien se da por vencido a la mitad de escribir la consulta completa.

    Afortunadamente, Lunr admite consultas más complejas, incluidas coincidencias aproximadas, comodines y lógica booleana (por ejemplo, AND, OR, NOT) para múltiples términos. Todos estos están disponibles mediante una sintaxis de consulta especial, por ejemplo:

    index.search("+*callum mac*")

    También podríamos recurrir al querymétodo index para manejarlo programáticamente.

    La primera solución no es satisfactoria ya que requiere un mayor esfuerzo por parte del usuario. Usé el index.querymétodo en su lugar:

    /* src/components/searchwidget.js */const search = index .query(function(q) { // full term matching q.term(el) // OR (default) // trailing or leading wildcard q.term(el, { wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING, }) }) .map(({ ref }) = { return { slug: ref, ...store[ref], } })

    ¿Por qué utilizar la coincidencia de términos completos con comodines? Esto es necesario para todas las palabras clave que "se benefician" del proceso de derivación. Por ejemplo, la raíz de "diferente" es "diferir". Como consecuencia, las consultas con comodines, como differe*, differen*o different*, no generan coincidencias, mientras que las consultas con términos completos y differedevuelven coincidencias.differendifferent

     

    También se pueden utilizar coincidencias aproximadas. En nuestro caso, se permiten únicamente para términos de cinco o más caracteres:

    q.term(el, { editDistance: el.length 5 ? 1 : 0 })q.term(el, { wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING,})

    La handleChangefunción también "limpia" las entradas del usuario e ignora los términos de un solo carácter:

    /* src/components/searchwidget.js */ const handleChange = e = { const query = e.target.value || "" setValue(query) if (!query.length) { setResults([]) } const keywords = query .trim() // remove trailing and leading spaces .replace(/*/g, "") // remove user's wildcards .toLowerCase() .split(/s+/) // split by whitespaces // do nothing if the last typed keyword is shorter than 2 if (keywords[keywords.length - 1].length 2) { return } try { const search = index .query(function(q) { keywords // filter out keywords shorter than 2 .filter(el = el.length 1) // loop over keywords .forEach(el = { q.term(el, { editDistance: el.length 5 ? 1 : 0 }) q.term(el, { wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING, }) }) }) .map(({ ref }) = { return { slug: ref, ...store[ref], } }) setResults(search) } catch (error) { console.log(error) }}

    Comprobémoslo en acción:

    • m- pendiente
    • ma– 861 coincidencias
    • mac– 600 partidos
    • macc– 35 partidos
    • macca– 12 partidos
    • maccal– 9 partidos
    • maccall– 9 partidos
    • maccallu– 3 partidos
    • maccallum– 3 partidos

    La búsqueda de "Callum" también funciona, lo que da como resultado cuatro coincidencias: Callum, MacCallum, MacCallum #2 y MacCallum de Berwick.

    Sin embargo, hay un problema más: las consultas con varios términos. Digamos que estás buscando "Loch Ness". Hay dos tartanes asociados con ese término, pero con la lógica OR predeterminada, obtienes un total de 96 resultados. (Hay muchos otros lagos en Escocia).

    Terminé decidiendo que una búsqueda AND funcionaría mejor para este proyecto. Desafortunadamente, Lunr no admite consultas anidadas y lo que realmente necesitamos es ( keyword1 OR *keyword*) AND (keyword2 OR *keyword2*).

    Para superar esto, terminé moviendo el bucle de términos fuera del querymétodo e intersectando los resultados por término. (Por intersección, me refiero a encontrar todas las babosas que aparecen en todos los resultados por palabra clave).

    /* src/components/searchwidget.js */try { // andSearch stores the intersection of all per-term results let andSearch = [] keywords .filter(el = el.length 1) // loop over keywords .forEach((el, i) = { // per-single-keyword results const keywordSearch = index .query(function(q) { q.term(el, { editDistance: el.length 5 ? 1 : 0 }) q.term(el, { wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING, }) }) .map(({ ref }) = { return { slug: ref, ...store[ref], } }) // intersect current keywordSearch with andSearch andSearch = i 0 ? andSearch.filter(x = keywordSearch.some(el = el.slug === x.slug)) : keywordSearch }) setResults(andSearch)} catch (error) { console.log(error)}

    El código fuente de tartanify.com está publicado en GitHub. Puede ver la implementación completa de la búsqueda Lunr allí.

    Pensamientos finales

    La búsqueda suele ser una característica no negociable para encontrar contenido en un sitio. La importancia real de la función de búsqueda puede variar de un proyecto a otro. Sin embargo, no hay razón para abandonarlo con el pretexto de que no coincide con el carácter estático de los sitios web Jamstack. Hay muchas posibilidades. Acabamos de hablar de uno de ellos.

    Y, paradójicamente, en este ejemplo específico, el resultado fue una mejor experiencia de usuario en todos los aspectos, gracias a que implementar la búsqueda no era una tarea obvia, sino que requería mucha deliberación. Es posible que no hubiéramos podido decir lo mismo con una solución de venta libre.






    SUSCRÍBETE A NUESTRO BOLETÍN 

    No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.










    Al suscribirte, aceptas nuestra política de privacidad y nuestros términos de servicio.






    Tal vez te puede interesar:

    1. La innovación no puede mantener la Web rápida
    2. Rendimiento web ultrarrápido
    3. Tabla de contenidos fijos con estados activos de desplazamiento
    4. “cambiar tamaño: ninguno;” en áreas de texto es una mala experiencia de usuario

    Cómo agregar Lunr Search a su sitio web de Gatsby

    Cómo agregar Lunr Search a su sitio web de Gatsby

    empezandoCreando un índice en gatsby-node.jsPágina de resultados de búsquedaWidget de búsqueda instantáneaHaciéndolo persistenteCrear una consulta de bú

    programar

    es

    https://pseint.es/static/images/programar-como-agregar-lunr-search-a-su-sitio-web-de-gatsby-1408-0.jpg

    2024-06-13

     

    Cómo agregar Lunr Search a su sitio web de Gatsby
    Cómo agregar Lunr Search a su sitio web de Gatsby

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Update cookies preferences