DEV Community

Cover image for Introduction to SolidStart Hooks
Mayuresh Smita Suresh
Mayuresh Smita Suresh Subscriber

Posted on

Introduction to SolidStart Hooks

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

  1. 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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

SEO Benefit: This pattern ensures your content is fully rendered on the server, making it immediately available to search engine crawlers.

  1. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

SEO Best Practices with SolidStart Hooks

  1. 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(', ')
    }
  };
}
Enter fullscreen mode Exit fullscreen mode
  1. 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} />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode
  1. 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 };
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Use useRouteData for server-side data fetching to improve SEO
  2. Implement proper meta tags with SolidStart's Meta components
  3. Create custom hooks for reusable logic like authentication and SEO
  4. Handle navigation states with useIsRouting for better UX
  5. Always consider server-side rendering for critical content

Top comments (0)