A Comprehensive Guide to SolidStart Hooks: Supercharge Your SolidJS Applications
SolidStart is the meta-framework for SolidJS that provides server-side rendering, file-based routing, and powerful data fetching capabilities out of the box. One of its most powerful features is the collection of hooks that streamline common tasks in modern web development. In this guide, we'll explore SolidStart hooks with practical examples and learn how to build SEO-friendly applications.
What Are Hooks in SolidStart?
Hooks are special functions that let you "hook into" SolidStart's features from your components. They provide access to routing, data fetching, server context, and more, making your code cleaner and more maintainable.
Essential SolidStart Hooks with Examples
- useRouteData() - Data Fetching Made Simple
The useRouteData hook is one of the most powerful features for server-side data fetching.
// routes/blog/[id].tsx
import { useRouteData } from "@solidjs/router";
import { createResource, Suspense } from "solid-js";
import { Meta, Title } from "@solidjs/meta";
// Server-side data fetching function
export function getData({ params, request }) {
const blogPost = await fetch(
`https://api.example.com/posts/${params.id}`
).then(res => res.json());
return { post: blogPost };
}
export default function BlogPost() {
// Access route data in component
const data = useRouteData<typeof getData>();
return (
<>
<Title>{data()?.post.title} | My Blog</Title>
<Meta name="description" content={data()?.post.excerpt} />
<Meta property="og:title" content={data()?.post.title} />
<Meta property="og:description" content={data()?.post.excerpt} />
<Suspense fallback={<div>Loading post...</div>}>
<article>
<h1>{data()?.post.title}</h1>
<div innerHTML={data()?.post.content} />
</article>
</Suspense>
</>
);
}
SEO Benefit: This pattern ensures your content is fully rendered on the server, making it immediately available to search engine crawlers.
- useParams() - Access Dynamic Route Parameters
Access parameters from dynamic routes with type safety.
// routes/products/[category]/[id].tsx
import { useParams } from "@solidjs/router";
import { createEffect } from "solid-js";
export default function ProductPage() {
const params = useParams();
createEffect(() => {
// React to parameter changes
console.log(`Loading product ${params.id} in category ${params.category}`);
// You can fetch additional client-side data here
fetchProductDetails(params.id);
});
return (
<div>
<h1>Product {params.id}</h1>
<p>Category: {params.category}</p>
</div>
);
}
- useNavigate() - Programmatic Navigation
Navigate between routes programmatically.
import { useNavigate } from "@solidjs/router";
import { createSignal } from "solid-js";
export default function SearchComponent() {
const navigate = useNavigate();
const [query, setQuery] = createSignal("");
const handleSearch = (e) => {
e.preventDefault();
// Navigate to search results page
navigate(`/search?q=${encodeURIComponent(query())}`);
// For SEO: Update canonical URL
updateCanonicalURL(`/search?q=${encodeURIComponent(query())}`);
};
return (
<form onSubmit={handleSearch}>
<input
type="text"
value={query()}
onInput={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<button type="submit">Search</button>
</form>
);
}
- useLocation() - Access Current URL Information
Get information about the current route.
import { useLocation } from "@solidjs/router";
import { createEffect } from "solid-js";
export default function AnalyticsTracker() {
const location = useLocation();
createEffect(() => {
// Track page views for SEO monitoring
if (typeof window !== 'undefined') {
// Send to analytics
trackPageView(location.pathname, location.search);
// Update structured data for current page
updateStructuredData(location.pathname);
}
});
return (
<div>
Current path: {location.pathname}
{location.search && <div>Query: {location.search}</div>}
</div>
);
}
- useIsRouting() - Detect Navigation State
Show loading indicators during route transitions.
import { useIsRouting } from "@solidjs/router";
import { Show } from "solid-js";
export default function NavigationLoader() {
const isRouting = useIsRouting();
return (
<Show when={isRouting()}>
<div class="page-transition-loader" aria-live="polite" aria-busy="true">
<div class="spinner"></div>
<span class="sr-only">Loading page...</span>
</div>
</Show>
);
}
- useBeforeLeave() - Handle Navigation Confirmation
Prompt users before leaving a page with unsaved changes.
import { useBeforeLeave } from "@solidjs/router";
import { createSignal } from "solid-js";
export default function FormWithWarning() {
const [hasUnsavedChanges, setHasUnsavedChanges] = createSignal(false);
useBeforeLeave((e) => {
if (hasUnsavedChanges()) {
const confirmed = confirm(
"You have unsaved changes. Are you sure you want to leave?"
);
if (!confirmed) {
e.preventDefault();
}
}
});
return (
<form onSubmit={(e) => {
e.preventDefault();
setHasUnsavedChanges(false);
// Submit form
}}>
<textarea
onInput={() => setHasUnsavedChanges(true)}
placeholder="Start typing..."
/>
<button type="submit">Save</button>
</form>
);
}
Building Custom Hooks in SolidStart
Create reusable logic with custom hooks that leverage SolidStart's capabilities.
Example: SEO Optimization Hook
// hooks/useSEO.ts
import { createMemo } from "solid-js";
import { useLocation } from "@solidjs/router";
export function useSEO(defaultMetadata) {
const location = useLocation();
const metaTags = createMemo(() => {
const path = location.pathname;
const fullUrl = `https://example.com${path}`;
return {
title: defaultMetadata.title,
description: defaultMetadata.description,
canonical: fullUrl,
og: {
title: defaultMetadata.title,
description: defaultMetadata.description,
url: fullUrl,
type: 'website',
image: defaultMetadata.image || 'https://example.com/default-og.jpg'
},
twitter: {
card: 'summary_large_image',
title: defaultMetadata.title,
description: defaultMetadata.description,
image: defaultMetadata.image
},
robots: {
index: defaultMetadata.index !== false,
follow: defaultMetadata.follow !== false
}
};
});
const generateStructuredData = (pageType, additionalData = {}) => {
const baseData = {
"@context": "https://schema.org",
"@type": pageType,
"url": `https://example.com${location.pathname}`,
...additionalData
};
return JSON.stringify(baseData);
};
return { metaTags, generateStructuredData, currentPath: location.pathname };
}
// Usage in component
import { useSEO } from "~/hooks/useSEO";
import { Meta, Title, Link } from "@solidjs/meta";
export default function ProductPage({ data }) {
const { metaTags, generateStructuredData } = useSEO({
title: data.product.name,
description: data.product.description,
image: data.product.image
});
const productSchema = generateStructuredData("Product", {
name: data.product.name,
description: data.product.description,
image: data.product.image,
offers: {
"@type": "Offer",
price: data.product.price,
priceCurrency: "USD"
}
});
return (
<>
<Title>{metaTags().title}</Title>
<Meta name="description" content={metaTags().description} />
<Meta property="og:title" content={metaTags().og.title} />
<Meta property="og:description" content={metaTags().og.description} />
<Meta property="og:image" content={metaTags().og.image} />
<Link rel="canonical" href={metaTags().canonical} />
<Meta name="robots" content={`${metaTags().robots.index ? 'index' : 'noindex'}, ${metaTags().robots.follow ? 'follow' : 'nofollow'}`} />
<script type="application/ld+json">
{productSchema}
</script>
<article>
<h1>{data.product.name}</h1>
{/* Product content */}
</article>
</>
);
}
Example: Authentication Hook with Route Protection
// hooks/useAuth.ts
import { createSignal, createEffect } from "solid-js";
import { useNavigate, useLocation } from "@solidjs/router";
export function createAuth() {
const [user, setUser] = createSignal(null);
const [loading, setLoading] = createSignal(true);
const navigate = useNavigate();
const location = useLocation();
// Initialize auth state
createEffect(() => {
const token = localStorage.getItem('auth_token');
if (token) {
fetchUserProfile(token).then(userData => {
setUser(userData);
setLoading(false);
}).catch(() => {
localStorage.removeItem('auth_token');
setLoading(false);
});
} else {
setLoading(false);
}
});
const login = async (credentials) => {
setLoading(true);
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
const data = await response.json();
localStorage.setItem('auth_token', data.token);
setUser(data.user);
navigate(location.query.redirect || '/dashboard');
} catch (error) {
throw error;
} finally {
setLoading(false);
}
};
const logout = () => {
localStorage.removeItem('auth_token');
setUser(null);
navigate('/login');
};
const requireAuth = (redirectTo = '/login') => {
if (!user() && !loading()) {
navigate(`${redirectTo}?redirect=${encodeURIComponent(location.pathname)}`);
return false;
}
return true;
};
return { user, loading, login, logout, requireAuth };
}
// Usage in a protected route
import { createAuth } from "~/hooks/useAuth";
export default function Dashboard() {
const auth = createAuth();
// Redirect if not authenticated
createEffect(() => {
if (!auth.loading() && !auth.user()) {
auth.requireAuth();
}
});
return (
<Show when={auth.user()} fallback={<div>Loading...</div>}>
<h1>Welcome, {auth.user().name}</h1>
<button onClick={auth.logout}>Logout</button>
</Show>
);
}
SEO Best Practices with SolidStart Hooks
- Server-Side Rendering for SEO
SolidStart's useRouteData ensures content is rendered on the server:
// This runs on the server, making content SEO-ready
export function getData({ params }) {
return {
product: await getProductFromDB(params.id),
// Add SEO-specific data
seo: {
title: `Buy ${product.name} Online`,
description: product.metaDescription,
keywords: product.tags.join(', ')
}
};
}
- Dynamic Meta Tags
Update meta tags based on route data:
import { useRouteData } from "@solidjs/router";
export default function DynamicSEO() {
const data = useRouteData();
return (
<>
<Title>{data().seo.title}</Title>
<Meta name="description" content={data().seo.description} />
<Meta name="keywords" content={data().seo.keywords} />
{/* Open Graph tags for social sharing */}
<Meta property="og:title" content={data().seo.title} />
<Meta property="og:description" content={data().seo.description} />
</>
);
}
- Canonical URLs and Pagination
Handle duplicate content issues:
import { useLocation } from "@solidjs/router";
export function useCanonicalURL(baseURL = 'https://example.com') {
const location = useLocation();
const getCanonicalURL = (page = 1) => {
const path = location.pathname;
// Remove pagination from canonical for page 1
if (page === 1 && path.includes('/page/')) {
return `${baseURL}${path.replace(/\/page\/\d+/, '')}`;
}
return `${baseURL}${path}`;
};
const getPaginationLinks = (currentPage, totalPages) => {
const links = [];
const basePath = location.pathname.replace(/\/page\/\d+/, '');
if (currentPage > 1) {
links.push({
rel: 'prev',
href: `${baseURL}${basePath}/page/${currentPage - 1}`
});
}
if (currentPage < totalPages) {
links.push({
rel: 'next',
href: `${baseURL}${basePath}/page/${currentPage + 1}`
});
}
return links;
};
return { getCanonicalURL, getPaginationLinks };
}
Performance Optimization with Hooks
Lazy Loading with createResource and useRouteData
import { createResource, Suspense } from "solid-js";
import { useRouteData } from "@solidjs/router";
export function getData({ params }) {
// Data fetches in parallel on server
return {
product: await fetchProduct(params.id),
reviews: await fetchReviews(params.id),
related: await fetchRelatedProducts(params.id)
};
}
export default function ProductPage() {
const data = useRouteData();
return (
<Suspense fallback={<ProductSkeleton />}>
<ProductDetails product={data().product} />
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews reviews={data().reviews} />
</Suspense>
<RelatedProducts products={data().related} />
</Suspense>
);
}
Conclusion
SolidStart hooks provide a powerful, intuitive way to build modern web applications with excellent SEO capabilities. By leveraging server-side rendering with useRouteData, managing navigation with useNavigate and useLocation, and creating custom hooks for specific needs, you can build performant, maintainable applications that rank well in search engines.
Remember these key takeaways:
- Use useRouteData for server-side data fetching to improve SEO
- Implement proper meta tags with SolidStart's Meta components
- Create custom hooks for reusable logic like authentication and SEO
- Handle navigation states with useIsRouting for better UX
- Always consider server-side rendering for critical content
Top comments (0)