- Opublikowano
Linki świadome SEO: automatyczny nofollow dla parametrów query w Nuxt
- Autorzy
Problem SEO z parametrami query
Sklepy e-commerce mają tysiące URL-i. Strona kategorii produktowej może wyglądać tak:
/products/kitchen-sinks ← Canonical
/products/kitchen-sinks?color=white ← Filtered
/products/kitchen-sinks?material=steel ← Filtered
/products/kitchen-sinks?sort=price-asc ← Sorted
/products/kitchen-sinks?page=2 ← Paginated
Google crawluje je wszystkie. Jeśli linki wewnętrzne prowadzą do przefiltrowanych wersji, rozcieńczasz PageRank na dziesiątki niemal identycznych URL-i, zamiast skupić go na stronie kanonicznej.
Poprawka po stronie SEO: dodać rel="nofollow" do linków z parametrami query (poza paginacją, którą chcesz mieć zaindeksowaną).
Podejście ręczne (żmudne)
<!-- Everywhere in your codebase -->
<NuxtLink :to="{ path: '/products', query: { color: 'white' } }" rel="nofollow">
White Products
</NuxtLink>
<NuxtLink :to="{ path: '/products', query: { page: 2 } }">
Page 2 <!-- No nofollow for pagination -->
</NuxtLink>
Wymaga to pamiętania o dodaniu rel="nofollow" do każdego przefiltrowanego linku. Developerzy zapomną. SEO na tym ucierpi.
Rozwiązanie zautomatyzowane
Stwórz komponent-wrapper, który automatycznie dodaje nofollow, gdy obecne są parametry query:
<!-- components/CustomNuxtLink.vue -->
<script setup lang="ts">
import type { NuxtLinkProps } from '#app'
const props = defineProps<NuxtLinkProps>()
const computedRel = computed(() => {
if (!props.to) {
return props.rel
}
let hasQueryParams = false
// Handle object-style :to prop
if (typeof props.to === 'object' && props.to !== null) {
const query = props.to.query
if (query && typeof query === 'object') {
const queryKeys = Object.keys(query)
if (queryKeys.length > 0) {
// Exception: pagination-only links should NOT be nofollow
const onlyPage = queryKeys.length === 1 && queryKeys[0] === 'page'
hasQueryParams = !onlyPage
}
}
}
// Handle string-style :to prop
else if (typeof props.to === 'string') {
const hasQuery = props.to.includes('?')
if (hasQuery) {
const urlParams = new URLSearchParams(props.to.split('?')[1])
const paramKeys = Array.from(urlParams.keys())
const onlyPage = paramKeys.length === 1 && paramKeys[0] === 'page'
hasQueryParams = !onlyPage
}
}
if (!hasQueryParams) {
return props.rel
}
// Add nofollow, preserving any existing rel value
return props.rel ? `${props.rel} nofollow` : 'nofollow'
})
</script>
<template>
<NuxtLink v-bind="{ ...props, rel: computedRel }">
<slot />
</NuxtLink>
</template>
Jak to działa
Zwykłe linki: bez zmian
<CustomNuxtLink to="/products">Products</CustomNuxtLink>
<!-- Renders: <a href="/products">Products</a> -->
<CustomNuxtLink :to="{ name: 'products-category', params: { category: 'sinks' } }">
Sinks
</CustomNuxtLink>
<!-- Renders: <a href="/products/sinks">Sinks</a> -->
Linki przefiltrowane: automatyczny nofollow
<CustomNuxtLink :to="{ path: '/products', query: { color: 'white' } }">
White Products
</CustomNuxtLink>
<!-- Renders: <a href="/products?color=white" rel="nofollow">White Products</a> -->
<CustomNuxtLink to="/products?material=steel">
Steel Products
</CustomNuxtLink>
<!-- Renders: <a href="/products?material=steel" rel="nofollow">Steel Products</a> -->
Paginacja: zwolniona z nofollow
<CustomNuxtLink :to="{ path: '/products', query: { page: 2 } }">
Page 2
</CustomNuxtLink>
<!-- Renders: <a href="/products?page=2">Page 2</a> -->
<!-- No nofollow! Pagination should be indexed -->
Parametry mieszane: dostają nofollow
<CustomNuxtLink :to="{ path: '/products', query: { color: 'white', page: 2 } }">
White Products - Page 2
</CustomNuxtLink>
<!-- Renders: <a href="/products?color=white&page=2" rel="nofollow">...</a> -->
<!-- Has nofollow because color param is present -->
Strategia migracji
Krok 1: stwórz komponent
Zapisz CustomNuxtLink.vue w katalogu components/.
Krok 2: globalne znajdź i zamień
Zamień wszystkie <NuxtLink na <CustomNuxtLink:
# Find all occurrences
grep -r "<NuxtLink" src/
# Or use your IDE's find & replace
# From: <NuxtLink
# To: <CustomNuxtLink
Krok 3: zaktualizuj tagi zamykające
# From: </NuxtLink>
# To: </CustomNuxtLink>
Krok 4: zweryfikuj
Sprawdź, czy linki renderują się poprawnie:
# Build and inspect HTML
npm run build
grep -r 'rel="nofollow"' .output/
Rozszerzanie wzorca
Linki zewnętrzne: zawsze nofollow
<script setup lang="ts">
const computedRel = computed(() => {
// ... existing query param logic ...
// External links always get nofollow
if (typeof props.to === 'string' && props.to.startsWith('http')) {
return props.rel ? `${props.rel} nofollow noopener` : 'nofollow noopener'
}
// ... rest of logic ...
})
</script>
Linki do API i assetów: nofollow + noindex
<script setup lang="ts">
const computedRel = computed(() => {
const to = props.to?.toString() ?? ''
// API and asset links
if (to.includes('/api/') || to.includes('/assets/')) {
return props.rel ? `${props.rel} nofollow noindex` : 'nofollow noindex'
}
// ... existing logic ...
})
</script>
Konkretne ścieżki do wykluczenia
<script setup lang="ts">
const nofollowPaths = ['/admin', '/dashboard', '/preview']
const computedRel = computed(() => {
const to = props.to?.toString() ?? ''
// Always nofollow certain paths
if (nofollowPaths.some((path) => to.startsWith(path))) {
return 'nofollow'
}
// ... existing logic ...
})
</script>
Obsłużone edge case'y
Zachowanie istniejących wartości rel
<CustomNuxtLink :to="{ path: '/docs', query: { tab: 'api' } }" rel="external">
API Docs
</CustomNuxtLink>
<!-- Renders: rel="external nofollow" -->
Puste obiekty query
<CustomNuxtLink :to="{ path: '/products', query: {} }">
Products
</CustomNuxtLink>
<!-- Renders: no rel (empty query doesn't count) -->
Prop to równy null lub undefined
<CustomNuxtLink :to="null">
Disabled Link
</CustomNuxtLink>
<!-- Handles gracefully, returns original rel -->
Korzyści z TypeScript
Użycie NuxtLinkProps daje pełne bezpieczeństwo typów:
import type { NuxtLinkProps } from '#app'
const props = defineProps<NuxtLinkProps>()
Wszystkie propsy NuxtLink działają automatycznie:
to(string | RouteLocationRaw)reltargetprefetchactiveClassexactActiveClass- i tak dalej
Kwestie wydajnościowe
computedRel uruchamia się raz na link przy renderowaniu. Nawet dla stron z setkami linków jest to pomijalne — to zwykłe parsowanie stringów.
A jeśli masz paranoję:
<script setup lang="ts">
// Memoize the result if to/rel don't change
const computedRel = computed(() => {
// ... logic ...
})
// Or use a static utility for SSR
const rel = computed(() => calculateRel(props.to, props.rel))
</script>
Weryfikacja wpływu na SEO
Przed: sprawdź obecne linki
# Crawl your site and count nofollow links
curl -s https://yoursite.com | grep -c 'rel="nofollow"'
Po: porównaj
Po migracji liczba ta powinna wyraźnie wzrosnąć na stronach z dużą liczbą filtrów.
Google Search Console
Obserwuj "Discovered - currently not indexed" w raporcie Coverage. Powinno pojawiać się mniej przefiltrowanych URL-i, bo Google respektuje nofollow.
Refaktor 89 plików
Kiedy to wdrażaliśmy, ruszyliśmy 89 plików. Migracja zajęła około godziny:
- Stworzenie komponentu (5 minut)
- Znajdź i zamień (10 minut)
- Build i testy (30 minut)
- Poprawki edge case'ów (15 minut)
Korzyść dla SEO jest trwała. Developerzy nie muszą już pamiętać o nofollow — dzieje się to automatycznie.
Kompletny komponent
<!-- components/CustomNuxtLink.vue -->
<script setup lang="ts">
import type { NuxtLinkProps } from '#app'
const props = defineProps<NuxtLinkProps>()
const computedRel = computed(() => {
if (!props.to) {
return props.rel
}
let hasQueryParams = false
if (typeof props.to === 'object' && props.to !== null) {
const query = props.to.query
if (query && typeof query === 'object') {
const queryKeys = Object.keys(query)
if (queryKeys.length > 0) {
const onlyPage = queryKeys.length === 1 && queryKeys[0] === 'page'
hasQueryParams = !onlyPage
}
}
} else if (typeof props.to === 'string') {
const hasQuery = props.to.includes('?')
if (hasQuery) {
const urlParams = new URLSearchParams(props.to.split('?')[1])
const paramKeys = Array.from(urlParams.keys())
const onlyPage = paramKeys.length === 1 && paramKeys[0] === 'page'
hasQueryParams = !onlyPage
}
}
if (!hasQueryParams) {
return props.rel
}
return props.rel ? `${props.rel} nofollow` : 'nofollow'
})
</script>
<template>
<NuxtLink v-bind="{ ...props, rel: computedRel }">
<slot />
</NuxtLink>
</template>
Wrzuć go do projektu, podmień linki i już nigdy nie martw się o nofollow na przefiltrowanych URL-ach.
Prawdziwy komponent ze sklepu e-commerce z tysiącami kombinacji filtrów produktowych. 89 plików zrefaktorowanych, SEO poprawione automatycznie.