Skip to content

Commit ffc7a2f

Browse files
authored
Merge pull request #145 from nuxt-modules/feat/crawler-nuxt-3
feat: add crawler support for Nuxt 3
2 parents 8b52c1a + 5181965 commit ffc7a2f

File tree

6 files changed

+88
-28
lines changed

6 files changed

+88
-28
lines changed

docs/content/2.advanced/5.crawler.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ description: ''
77

88
> This section only applies if you are using Nuxt static site generation.
99
10-
When setting the `crawler` option of this module (see [Setup](/getting-started/configuration#crawler)), the module will automatically collect your pages' metadata and store them in an Algolia index. This allows you to easily add a search functionality on your website or blog without worrying about updating your indices as the process happens during the Nuxt static generation. This feature works with both Nuxt 2 and Bridge, and will support Nuxt 3 as soon as SSG is fully implemented.
10+
When setting the `crawler` option of this module (see [Setup](/getting-started/configuration#crawler)), the module will automatically collect your pages' metadata and store them in an Algolia index. This allows you to easily add a search functionality on your website or blog without worrying about updating your indices as the process happens during the Nuxt static generation.

playground/nuxt.config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ export default defineNuxtConfig({
44
modules: [
55
AlgoliaModule
66
],
7+
nitro: {
8+
prerender: {
9+
crawlLinks: true
10+
},
11+
routeRules: {
12+
'/': {
13+
prerender: true
14+
}
15+
}
16+
},
717
algolia: {
818
// apiKey: process.env.ALGOLIA_API_KEY,
919
// applicationId: process.env.ALGOLIA_APPLICATION_ID,
@@ -24,6 +34,10 @@ export default defineNuxtConfig({
2434
indexName: process.env.ALGOLIA_INDEX_NAME,
2535
accessToken: process.env.STORYBLOK_ACCESS_TOKEN
2636
}
37+
},
38+
crawler: {
39+
apiKey: process.env.ALGOLIA_CRAWLER_API_KEY,
40+
indexName: process.env.ALGOLIA_CRAWLER_INDEX_NAME
2741
}
2842
}
2943
})

playground/pages/index.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@
1515
<h3>DocSearch plugin</h3>
1616
<AlgoliaDocSearch :options="docSearch" />
1717
</div>
18+
<NuxtLink to="/other-page">
19+
Other page
20+
</NuxtLink>
1821
</div>
1922
</template>
2023

2124
<script lang="ts" setup>
25+
import { useSeoMeta } from '@unhead/vue'
2226
import { AisInstantSearch, AisSearchBox, AisHits } from 'vue-instantsearch/vue3/es'
2327
// Grab DocSearch config from nuxt.config
2428
// (the component does that by itself as well)
@@ -63,4 +67,12 @@ onMounted(async () => {
6367
// There should be no error
6468
typedFoo.hits[0].foo = '1'
6569
})
70+
71+
useHead({
72+
title: 'Nuxt Algolia'
73+
})
74+
75+
useSeoMeta({
76+
description: 'Playground for @nuxtjs/algolia'
77+
})
6678
</script>

playground/pages/other-page.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script setup lang="ts">
2+
import { useSeoMeta } from '@unhead/vue'
3+
4+
useHead({
5+
title: 'Other Page'
6+
})
7+
8+
useSeoMeta({
9+
description: 'Dolore amet culpa culpa dolore cupidatat dolor magna amet elit aute qui proident. Eu ea aliquip ipsum est. Incididunt aliqua in consequat reprehenderit adipisicing id laboris eiusmod voluptate laboris tempor ullamco ipsum. Mollit culpa non dolore aute deserunt eiusmod pariatur enim sunt aute consequat quis. Quis sint enim consectetur velit ex ullamco. Dolor non aliqua elit anim reprehenderit ut sit ullamco nulla ut consectetur. Occaecat sit fugiat id consectetur nostrud aute dolore anim ex irure.'
10+
})
11+
</script>
12+
13+
<template>
14+
<div />
15+
</template>

src/hooks/crawler.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11

2-
import type { Nuxt, NuxtHooks } from '@nuxt/schema'
32
import algoliasearch from 'algoliasearch'
43
import scraper from 'metadata-scraper'
54
import type { SearchClient, SearchIndex } from 'algoliasearch'
65
import type { MetaData } from 'metadata-scraper/lib/types'
6+
import { Nuxt } from '@nuxt/schema'
77
import type { ModuleOptions } from './../module'
88

9-
// TODO: Nuxt 2 only
10-
// export type GeneratePageArg = Parameters<NuxtHooks['generate:page']>[0]
11-
export type GeneratePageArg = any
12-
139
export type CrawlerPage = { href: string } & MetaData
1410

11+
type CrawlerMetaGetter = ((html: string, route: string) => MetaData | Promise<MetaData>)
12+
type CrawlerIncludeFilter = ((route: string) => boolean)
13+
14+
export interface CrawlerOptions {
15+
apiKey: string;
16+
indexName: string;
17+
meta?: CrawlerMetaGetter | (keyof MetaData)[]
18+
include?: CrawlerIncludeFilter | (string | RegExp)[]
19+
}
20+
21+
export interface CrawlerConfig extends CrawlerOptions {
22+
meta: CrawlerMetaGetter
23+
include: CrawlerIncludeFilter
24+
}
25+
1526
/**
1627
* Create a function to specify which routes should be indexed.
1728
*/
18-
function createShouldInclude (options: ModuleOptions) {
29+
function createShouldInclude (options: ModuleOptions): CrawlerIncludeFilter {
1930
const { include } = options.crawler
2031

2132
return typeof include === 'function'
@@ -26,7 +37,7 @@ function createShouldInclude (options: ModuleOptions) {
2637
/**
2738
* Create a function to collect the routes' metadata.
2839
*/
29-
function createMetaGetter (options: ModuleOptions) {
40+
function createMetaGetter (options: ModuleOptions): CrawlerMetaGetter {
3041
const { meta } = options.crawler
3142

3243
if (typeof meta === 'function') {
@@ -61,11 +72,11 @@ function createDefaultMetaGetter () {
6172
/**
6273
* Create the "page:generate" hook callback to collect all the included routes' metadata.
6374
*/
64-
export function createPageGenerateHook (nuxt, options: ModuleOptions, pages: CrawlerPage[]) {
75+
export function createPageGenerateHook (nuxt: Nuxt, options: ModuleOptions, pages: CrawlerPage[]) {
6576
const shouldInclude = createShouldInclude(options)
6677
const getMeta = createMetaGetter(options)
6778

68-
return async ({ html, route }: GeneratePageArg) => {
79+
return async (html: string, route: string) => {
6980
if (shouldInclude(route)) {
7081
const meta = await getMeta(html, route)
7182
const page = { href: route, ...meta }
@@ -92,7 +103,7 @@ export function createPageGenerateHook (nuxt, options: ModuleOptions, pages: Cra
92103
/**
93104
* Create the "generate:done" hook callback to index the collected routes' metadata.
94105
*/
95-
export function createGenerateDoneHook (nuxt, options: ModuleOptions, pages: CrawlerPage[]) {
106+
export function createGenerateDoneHook (nuxt: Nuxt, options: ModuleOptions, pages: CrawlerPage[]) {
96107
return async () => {
97108
if (pages.length > 0 && options.crawler) {
98109
const { crawler: { apiKey, indexName }, applicationId } = options
@@ -128,5 +139,5 @@ export interface CrawlerHooks {
128139
}
129140

130141
declare module '@nuxt/schema' {
131-
interface NuxtHooks extends CrawlerHooks {}
142+
interface NuxtHooks extends CrawlerHooks { }
132143
}

src/module.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { resolve } from 'path'
22
import { fileURLToPath } from 'url'
3-
import { defineNuxtModule, addPlugin, addComponentsDir, addServerHandler, addImportsDir, isNuxt2, extendViteConfig } from '@nuxt/kit'
4-
import type { MetaData } from 'metadata-scraper/lib/types'
3+
import { defineNuxtModule, addPlugin, addComponentsDir, addServerHandler, addImportsDir, isNuxt2 } from '@nuxt/kit'
54
import { defu } from 'defu'
6-
import { createPageGenerateHook, createGenerateDoneHook, CrawlerPage, CrawlerHooks } from './hooks'
5+
import { createPageGenerateHook, createGenerateDoneHook, CrawlerPage, CrawlerHooks, CrawlerOptions } from './hooks'
76
import type { DocSearchOptions } from './types'
87

98
enum InstantSearchThemes {
@@ -34,14 +33,7 @@ interface ModuleBaseOptions {
3433
}
3534

3635
export interface ModuleOptions extends ModuleBaseOptions {
37-
crawler?: {
38-
apiKey: string;
39-
indexName: string;
40-
meta:
41-
| ((html: string, route: string) => MetaData|Promise<MetaData>)
42-
| (keyof MetaData)[]
43-
include: ((route: string) => boolean) | (string | RegExp)[]
44-
}
36+
crawler?: CrawlerOptions
4537
};
4638

4739
export interface ModuleHooks extends CrawlerHooks {}
@@ -95,11 +87,27 @@ export default defineNuxtModule<ModuleOptions>({
9587

9688
const pages: CrawlerPage[] = []
9789

98-
nuxt.addHooks({
90+
const pageGenerator = createPageGenerateHook(nuxt, options, pages)
91+
const doneGenerator = createGenerateDoneHook(nuxt, options, pages)
92+
93+
if (isNuxt2(nuxt)) {
94+
nuxt.addHooks({
9995
// @ts-expect-error Nuxt 2 only hook
100-
'generate:page': createPageGenerateHook(nuxt, options, pages),
101-
'generate:done': createGenerateDoneHook(nuxt, options, pages)
102-
})
96+
'generate:page': createPageGenerateHook(nuxt, options, pages),
97+
'generate:done': createGenerateDoneHook(nuxt, options, pages)
98+
})
99+
} else {
100+
nuxt.hooks.hookOnce('nitro:init', (nitro) => {
101+
nitro.hooks.hookOnce('prerender:routes', () => {
102+
nitro.hooks.hook('prerender:route', async ({ route, contents }) => {
103+
await pageGenerator(contents, route)
104+
})
105+
nitro.hooks.hookOnce('close', async () => {
106+
await doneGenerator()
107+
})
108+
})
109+
})
110+
}
103111
}
104112

105113
if (Object.keys(options.docSearch!).length) {
@@ -165,7 +173,7 @@ export default defineNuxtModule<ModuleOptions>({
165173
nuxt.hook('vite:extendConfig', (config, { isClient }) => {
166174
if (isClient) {
167175
(config as any).resolve.alias['@algolia/requester-node-http'] =
168-
'unenv/runtime/mock/empty';
176+
'unenv/runtime/mock/empty'
169177
}
170178
})
171179

0 commit comments

Comments
 (0)