DEV Community

Cover image for GoRouter for Flutter Web: Building a URL-Friendly App
Bestaoui Aymen
Bestaoui Aymen

Posted on

GoRouter for Flutter Web: Building a URL-Friendly App

Why GoRouter for Flutter Web?

GoRouter, developed by the Flutter team, is designed to handle navigation with a URL-based approach, making it ideal for web apps. Here’s why GoRouter shines for Flutter web development:

  • URL-Based Navigation: GoRouter supports clean URLs (e.g., /about, /product/123), which align with how users expect web navigation to work.
  • Browser Integration: It handles browser back/forward buttons and URL changes seamlessly.
  • Deep Linking: Users can share or bookmark specific pages (e.g., /product/42) and land directly on them.
  • Declarative Routing: Define routes in a straightforward, scalable way, reducing navigation complexity.

In this guide, we’ll build a simple Flutter web app with GoRouter, featuring a Home page, an About page, and a Product page with dynamic URLs. We’ll ensure the app supports browser navigation and deep linking, all while keeping the code beginner-friendly.

Setting Up the Project

Let’s create a Flutter web app with GoRouter step by step.

Step 1: Create a New Flutter Project

If you don’t already have a Flutter project, create one by running:

flutter create go_router_web_app
cd go_router_web_app
Enter fullscreen mode Exit fullscreen mode

Ensure your Flutter SDK is set up for web development. Test it with:

flutter run -d chrome
Enter fullscreen mode Exit fullscreen mode

Step 2: Add GoRouter Dependency

Add the go_router package to your project. Open pubspec.yaml and include:

dependencies:
  go_router: ^16.2.1
Enter fullscreen mode Exit fullscreen mode

Run flutter pub get to install it. (Check pub.dev for the latest version.)

Step 3: Create the App Screens

Let’s create three simple screens: HomeScreen, AboutScreen, and ProductScreen. Create a lib/screens folder and add the following files.

lib/screens/home_screen.dart:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Welcome to the Home Page!'),
            ElevatedButton(
              onPressed: () => context.go('/about'),
              child: const Text('Go to About'),
            ),
            ElevatedButton(
              onPressed: () => context.go('/product/123'),
              child: const Text('View Product #123'),
            ),
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

lib/screens/about_screen.dart:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class AboutScreen extends StatelessWidget {
  const AboutScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('About')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('About Our App'),
            ElevatedButton(
              onPressed: () => context.go('/'),
              child: const Text('Back to Home'),
            ),
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

lib/screens/product_screen.dart:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class ProductScreen extends StatelessWidget {
  final String productId;
  const ProductScreen({super.key, required this.productId});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Product #$productId')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Details for Product #$productId'),
            ElevatedButton(
              onPressed: () => context.go('/'),
              child: const Text('Back to Home'),
            ),
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Set Up GoRouter

Now, configure GoRouter to handle navigation. Replace the contents of lib/main.dart with the following:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'screens/home_screen.dart';
import 'screens/about_screen.dart';
import 'screens/product_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // Define the GoRouter configuration
  final GoRouter _router = GoRouter(
    initialLocation: '/', // Start at the home page
    routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => const HomeScreen(),
      ),
      GoRoute(
        path: '/about',
        builder: (context, state) => const AboutScreen(),
      ),
      GoRoute(
        path: '/product/:productId',
        builder: (context, state) {
          final productId = state.pathParameters['productId']!;
          return ProductScreen(productId: productId);
        },
      ),
    ],
    // Optional: Handle 404-like errors
    errorBuilder: (context, state) => Scaffold(
      appBar: AppBar(title: const Text('Page Not Found')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Error: ${state.error}'),
            ElevatedButton(
              onPressed: () => context.go('/'),
              child: const Text('Go to Home'),
            ),
          ],
        ),
      ),
    ),
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Flutter GoRouter Web Demo',
      routerConfig: _router,
      theme: ThemeData(primarySwatch: Colors.blue),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Explanation of the Code

  • GoRouter Setup: We define three routes:
    • /: Maps to HomeScreen.
    • /about: Maps to AboutScreen.
    • /product/:productId: Maps to ProductScreen, with a dynamic productId parameter.
  • MaterialApp.router: This integrates GoRouter with Flutter’s navigation system.
  • Error Handling: The errorBuilder displays a custom 404 page if the user navigates to an invalid route (e.g., /invalid).
  • Navigation: We use context.go() for navigation, which updates the browser’s URL and supports back/forward buttons.

Step 5: Run the App for Web

Run the app in a browser with:

flutter run -d chrome
Enter fullscreen mode Exit fullscreen mode

You should see:

  • The Home page with buttons to navigate to the About page or Product page.
  • URLs in the browser change to /, /about, or /product/123 as you navigate.
  • Browser back/forward buttons work seamlessly.
  • Typing a URL like http://localhost:port/product/456 directly in the browser takes you to the Product page with ID 456.

Key GoRouter Features for Web Apps

1. URL-Based Navigation

GoRouter makes URLs the core of navigation. For example, navigating to /product/123 with context.go('/product/123') updates the browser’s address bar, making the URL shareable.

2. Deep Linking

GoRouter supports deep linking out of the box. If a user visits http://yourapp.com/product/123, GoRouter parses the productId and loads the correct screen. This is great for sharing links or integrating with external systems like notifications.

3. Browser History Integration

GoRouter syncs with the browser’s history. Clicking the back button returns to the previous route (e.g., from /about to /), and forward navigates back. This happens automatically, no extra code needed.

4. Dynamic Routes

The :productId syntax allows dynamic routes. You can access the parameter in the builder with state.pathParameters['productId']. For example:

GoRoute(
  path: '/product/:productId',
  builder: (context, state) => ProductScreen(productId: state.pathParameters['productId']!),
)
Enter fullscreen mode Exit fullscreen mode

5. Query Parameters

GoRouter also supports query parameters (e.g., /search?query=flutter). Access them with state.uri.queryParameters['query']. For example:

GoRoute(
  path: '/search',
  builder: (context, state) {
    final query = state.uri.queryParameters['query'] ?? 'No query';
    return SearchScreen(query: query);
  },
)
Enter fullscreen mode Exit fullscreen mode

6. Redirects for Authentication

For web apps, you might want to redirect users to a login page if they’re not authenticated. Add a redirect to the GoRouter config:

final GoRouter _router = GoRouter(
  routes: [/* ... */],
  redirect: (context, state) {
    bool isLoggedIn = false; // Replace with your auth logic
    if (!isLoggedIn && state.uri.path != '/login') {
      return '/login';
    }
    return null; // No redirect
  },
);
Enter fullscreen mode Exit fullscreen mode

Tips for Building URL-Friendly Flutter Web Apps

  • Test URLs Directly: Try typing URLs like /product/999 in the browser to ensure deep linking works.
  • Use Descriptive Paths: Keep routes like /about or /product intuitive for users.
  • Handle Errors Gracefully: Use errorBuilder to show user-friendly 404 pages.
  • Optimize for SEO: Clean URLs like /product/123 are better for search engines and sharing.
  • Test Browser Navigation: Ensure back/forward buttons work as expected.

Deploying the Web App

To deploy your Flutter web app:

  1. Build it with:
   flutter build web
Enter fullscreen mode Exit fullscreen mode
  1. Host the contents of the build/web folder on a web server (e.g., Firebase Hosting, Netlify, or GitHub Pages).
  2. Ensure your server supports single-page app routing, redirecting all routes to index.html for GoRouter to handle.

Top comments (0)