๐ŸŽ‰ QZ-L.com Now Serves 300+ Short Link Redirects!

Great news! QZ-L.com has officially passed 300 active short-link redirects — a big milestone for our lightweight and fast link-management service.

Whether you're using QZ-L.com for product launches, personal projects, or API testing, every redirect is powered by our optimized infrastructure designed for speed, reliability, and simplicity.

Thank you to everyone using QZ-L. More improvements, analytics, and developer tools are coming soon! ๐Ÿš€




Checkout https://qz-l.com

Building the qz-l AI Chat Assistant Using Google Gemini 2.5 Flash Lite + Next.js

 

Building the qz-l AI Chat Assistant Using Google Gemini 2.5 Flash Lite + Next.js

Learn how qz-l.com's new AI chat assistant works using Gemini 2.5 Flash Lite, Next.js API routes, and function calling --- all free on the Google AI tier.

November 20, 2025By qz-l team

๐Ÿค– Building the qz-l AI Chat Assistant Using Google Gemini 2.5 Flash Lite + Next.js

I recently added a new feature to qz-l.com: an AI-powered chat assistant that can shorten URLs, show analytics, search blog posts, and help users navigate the service --- all through natural language.

This post explains how the assistant works under the hood using the modern @google/genai SDK and the free-tier model gemini-2.5-flash-lite, which runs entirely at zero cost within Google's usage limits.

๐Ÿš€ Why Build an AI Assistant?

qz-l's mission is simple: privacy-first URL shortening with analytics.

But users often ask:

  • "How do I create a short link?"
  • "Where's the dashboard?"
  • "Can I delete a URL?"
  • "What does this blog post say?"

Instead of building a whole help UI, I added a chat interface that can perform real actions using function calling.

๐Ÿง  Model Choice: gemini-2.5-flash-lite (Free)

The assistant uses:

  • SDK: @google/genai
  • Model: gemini-2.5-flash-lite
  • Platform: Google AI Studio (free tier)

Why this model?

  • ✓ Completely free within quota
  • ✓ Very fast and low latency
  • ✓ Full function calling support
  • ✓ Perfect for automation + chat
  • ✓ Stable enough for production workloads

Inspired by this resource list: https://github.com/cheahjs/free-llm-api-resources

๐Ÿ—️ System Architecture

The assistant lives inside a Next.js App Router API route:

/api/chat

High-level flow:

User Message
    ↓
Next.js API (/api/chat)
    ↓
Gemini LLM (with system prompt + tools)
    ↓
If function call → server executes logic
    ↓
LLM formats final Markdown response
    ↓
Chat UI displays answer (links, QR codes, etc.)

⚙️ Using @google/genai

import { GoogleGenAI } from "@google/genai";

const ai = new GoogleGenAI({ apiKey });
const result = await ai.models.generateContent({
  model: "gemini-2.5-flash-lite",
  contents,
  config: {
    temperature: 0.7,
    maxOutputTokens: 1024,
    systemInstruction: {
      role: "system",
      parts: [{ text: SYSTEM_PROMPT }],
    },
    tools: [
      {
        functionDeclarations: [
          shortenUrlDeclaration,
          getUrlAnalyticsDeclaration,
          listRecentUrlsDeclaration,
          deleteUrlDeclaration,
          searchBlogPostsDeclaration,
        ],
      },
    ],
  },
});

๐Ÿงฉ Function Calling

const shortenUrlDeclaration = {
  name: "shortenUrl",
  description: "Generate a shortened URL",
  parameters: {
    type: "object",
    properties: {
      longUrl: { type: "string" },
    },
    required: ["longUrl"],
  },
};

Example function call return:

{
  "functionCall": {
    "name": "shortenUrl",
    "args": { "longUrl": "https://google.com" }
  }
}

๐Ÿงต The Function-Call Loop

  1. Ask Gemini for the next message
  2. Detect if it requested a function
  3. Execute the function on the server
  4. Append the result
  5. Call Gemini again for the final answer

๐ŸŽฏ Why This Approach Works

  • ✓ Zero cost (Gemini free tier)
  • ✓ Fast responses
  • ✓ Deterministic output
  • ✓ Secure
  • ✓ Extendable

๐Ÿ“ˆ Current Capabilities

  • Shorten URLs
  • Generate QR codes
  • Fetch analytics
  • Delete links
  • Show recent URLs
  • Search blog posts
  • Explain features

๐Ÿ”ฎ Coming Enhancements

  • Auth-protected actions
  • Rate limiting
  • Streaming responses
  • Better UI

๐ŸŽ‰ Final Thoughts

You don't need expensive models to build production AI features --- just solid architecture and a good system prompt.

How to Set Up Giscus Comments for Your Website

 

How to Set Up Giscus Comments for Your Website

If you want to add a clean, GitHub-powered comment system to your website, Giscus is one of the best options.
This guide walks you step-by-step through everything required:

  • Creating a comments repository

  • Enabling Discussions

  • Installing the Giscus GitHub App

  • Getting your repoId and categoryId via GitHub GraphQL

  • Configuring the <Giscus /> component in your site

By the end, you’ll have a fully working comment system—powered entirely by GitHub Discussions.


1. Create a Repository for Storing Comments

Create a new GitHub repository dedicated to comments:

Q-Z-L-Corp/giscus-comments

This is where all the comments from your website will be stored as GitHub Discussions.


2. Enable GitHub Discussions for the Repo

Go to:

Settings → General → Features → Discussions → ✔ Enable

Without Discussions enabled, Giscus cannot store or load comments.


3. Install the Giscus GitHub App

Giscus relies on its GitHub App to read and write discussions.

Install it here:
https://github.com/apps/giscus

During installation:

  • Select your GitHub account or organization

  • Select the comments repo (e.g., Q-Z-L-Corp/giscus-comments)

  • Grant required permissions (Discussions, Contents, Metadata)

Once installed, Giscus is authorized to create and read discussion threads.


4. Get Your GitHub Repository ID (repoId)

Giscus requires the GraphQL repository ID, not the repo name.

To query it, you need a GitHub personal access token (PAT) with:

  • public_repo (for public repos)

  • or repo (for private repos)

Then send this GraphQL query:

GraphQL Query – Get Repo ID

query { repository(owner: "Q-Z-L-Corp", name: "giscus-comments") { id name } }

Example Response

{ "data": { "repository": { "id": "R_kgDOQX9xSQ", "name": "giscus-comments" } } }

Your repoId is:

R_kgDOQX9xSQ

5. Get Your Discussion Category ID (categoryId)

Your repo must have at least one discussion category.
Giscus typically uses something like “Blog Comments”.

Get your category ID with this query:

GraphQL Query – Get Discussion Category ID

query { repository(owner: "Q-Z-L-Corp", name: "giscus-comments") { discussionCategories(first: 20) { nodes { id name slug description } } } }

Example Response

{ "data": { "repository": { "discussionCategories": { "nodes": [ { "id": "DIC_kwDOQX9xSc4Cx7Fn", "name": "Blog Comments", "slug": "blog-comments" } ] } } } }

Your categoryId is:

DIC_kwDOQX9xSc4Cx7Fn

6. Use the Values in the Giscus Component

Now you can embed Giscus into your website.

Here’s a working example:

import Giscus from "@giscus/react"; export default function Comments() { return ( <Giscus id="comments" repo="Q-Z-L-Corp/giscus-comments" repoId="R_kgDOQX9xSQ" category="Blog Comments" categoryId="DIC_kwDOQX9xSc4Cx7Fn" mapping="pathname" reactionsEnabled="1" emitMetadata="0" inputPosition="bottom" theme="light" lang="en" loading="lazy" /> ); }

This will automatically create a discussion for each page and load all comments.


7. Done — Giscus Is Live

You’ve now completed all required steps:

✔ Created comment repo
✔ Enabled Discussions
✔ Installed Giscus GitHub App
✔ Retrieved repoId
✔ Retrieved categoryId
✔ Embedded Giscus configuration

Your website now has a GitHub-powered commenting system with:

  • zero backend

  • no user accounts to manage

  • spam-resistant GitHub authentication

  • clean UX

Building a Real-Time Analytics Dashboard for URL Shorteners

 

Building a Real-Time Analytics Dashboard for URL Shorteners

One of the most powerful features of a URL shortener is the ability to track and analyze how your links perform. Today, we're sharing how we built qz-l's comprehensive analytics dashboard—a system that gives users instant insights into their traffic patterns, audience demographics, and referral sources.

The Challenge

When we started building the analytics feature, we knew we needed:

  • Fast data retrieval: Users want instant results when they search for a link
  • Comprehensive insights: Not just page views, but traffic sources, device types, browsers, and time patterns
  • Beautiful visualizations: Raw numbers are boring; interactive charts tell the story
  • Mobile-responsive design: Analytics should work everywhere
  • User agent intelligence: Convert cryptic UA strings into readable "Chrome on macOS" format

Architecture Overview

Database Schema

The foundation is a single link_visits table that captures everything about each visit:

CREATE TABLE link_visits (
  id BIGSERIAL PRIMARY KEY,
  short_link_id BIGINT REFERENCES short_links(id),
  ip_address INET,
  user_agent TEXT,
  referrer TEXT,
  language TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX ON link_visits(short_link_id);
CREATE INDEX ON link_visits(created_at);

Every time someone clicks a shortened link, we capture:

  • IP Address: For geo-location and unique visitor counting
  • User Agent: Browser, OS, and device information
  • Referrer: Where the click came from
  • Language: Browser language preference
  • Timestamp: When the visit occurred

Backend: 8 Parallel Queries

Instead of running queries sequentially, we use Promise.all() to fetch all analytics data in parallel:

const [
  totalVisits,
  uniqueVisitors,
  topReferrers,
  topUserAgents,
  languages,
  dailyVisits,
  hourlyVisits
] = await Promise.all([
  query(`SELECT COUNT(*) as count FROM link_visits WHERE short_link_id = $1`, [linkId]),
  query(`SELECT COUNT(DISTINCT ip_address) as count FROM link_visits WHERE short_link_id = $1`, [linkId]),
  query(`SELECT referrer, COUNT(*) as count FROM link_visits WHERE short_link_id = $1 GROUP BY referrer ORDER BY count DESC LIMIT 20`, [linkId]),
  // ... more queries
]);

This parallel approach means the entire analytics dashboard loads in ~200ms regardless of how many queries we need.

Key Features

1. User Agent Parsing

Raw user agent strings look like this:

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

That's useful for debugging, but not for business analytics. We built a parser that extracts meaningful information:

export function parseUserAgent(ua: string): UserAgentInfo {
  const osMatch = ua.match(/Windows|Macintosh|Linux|iPhone|iPad|Android/);
  const browserMatch = ua.match(/Chrome|Firefox|Safari|Edge|Opera/);
  const deviceMatch = ua.match(/Mobile|Tablet|Desktop/);
  
  return {
    os: extractOS(ua),
    browser: extractBrowser(ua),
    device: detectDevice(ua),
    label: `${extractBrowser(ua)} on ${extractOS(ua)}`
  };
}

Now those cryptic strings become: "Chrome on macOS", "Safari on iOS", "Firefox on Windows"—much better for understanding your audience!

2. Interactive Charts with Recharts

We use Recharts for professional, responsive visualizations:

30-Day Trend (Line Chart)

Shows visitor growth over the past month with a smooth line chart. Perfect for spotting trends and seasonal patterns.

24-Hour Distribution (Column Chart)

Horizontal scrolling column chart showing which hours get the most traffic. Helps determine when your audience is most active.

Top Referrers (Horizontal Bar Chart)

See where your traffic comes from: direct links, specific websites, social media platforms, etc. Shows domain names clearly with color-coded bars.

Platform & Browser Distribution (Pie Chart)

At a glance, see what devices and browsers your visitors use. This helps prioritize which platforms to support.

3. Key Metrics

Every analytics view displays four primary metrics:

  • Total Views: Raw page view count
  • Unique Visitors: Deduplicated by IP address
  • Conversion Rate: Unique visitors as a percentage of total views
  • Created Date: When the link was created
<StatsCard label="Total Views" value={analytics.totalVisits} icon="๐Ÿ‘️" />
<StatsCard label="Unique Visitors" value={analytics.uniqueVisitors} icon="๐Ÿ‘ฅ" />
<StatsCard label="Conversion Rate" value={`${conversionRate}%`} icon="๐Ÿ“Š" />

Mobile Responsiveness: A Deep Dive

Building analytics for mobile is tricky. Charts need space, but mobile screens are limited. Here's how we solved it:

Responsive Data Limiting

On mobile, we don't overwhelm users with 10 data points—we show 5:

const [isMobile, setIsMobile] = useState(false);

useEffect(() => {
  setIsMobile(window.innerWidth < 768);
  const handleResize = () => setIsMobile(window.innerWidth < 768);
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);

const limit = isMobile ? 5 : 10;
const displayData = data.slice(0, limit);

Adaptive Chart Layouts

Different charts need different strategies:

Horizontal Bar Charts (Referrers)

  • Mobile: Horizontal scrolling container (fixed 400px width)
  • Desktop: Full width, no scrolling

Pie Charts (Platforms)

  • Mobile: Pie at center (cx="50%"), horizontal legend below
  • Desktop: Pie offset left (cx="40%"), vertical legend on right

Line Charts (30-Day Trend)

  • Both: ResponsiveContainer handles scaling automatically

Overflow Prevention

The killer issue on mobile is horizontal scrollbars. We prevent them with:

<main className="max-w-7xl mx-auto px-4 sm:px-6 py-8 overflow-x-hidden">
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
    {/* Charts wrapped in overflow-hidden */}
    <div className="overflow-hidden">
      <ReferrersChart />
    </div>
  </div>
</main>

Search Functionality

Users can search by either short code or original URL:

const handleSearch = async (e) => {
  const searchParam = searchInput.includes("/")
    ? `originalUrl=${encodeURIComponent(searchInput)}`
    : `shortCode=${encodeURIComponent(searchInput)}`;
  
  const response = await fetch(`/api/analytics?${searchParam}`);
};

This flexibility means users don't need to remember their short codes—they can search by the original URL they shortened.

Performance Optimizations

1. Database Indexes

CREATE INDEX ON link_visits(short_link_id);
CREATE INDEX ON link_visits(created_at);

These indexes make queries fast even with millions of visits.

2. Parallel Query Execution

Using Promise.all() reduces response time from 800ms (sequential) to 200ms (parallel).

3. Client-Side Rendering

The analytics page is a client component, allowing instant interactivity without waiting for server-side rendering.

4. Responsive Container Scaling

Recharts' ResponsiveContainer handles chart resizing without re-fetching data.

Tech Stack

ComponentTechnology
Frontend FrameworkNext.js 15
ChartsRecharts
StylingTailwind CSS
LanguageTypeScript
DatabasePostgreSQL
User Agent ParsingCustom parser + regex

Future Enhancements

As we expand the analytics feature, we're planning:

  • Geographic insights: Map showing traffic by country
  • Export analytics: Download as CSV or PDF
  • Scheduled reports: Email analytics summaries
  • Comparison mode: Compare multiple links side-by-side
  • Anomaly detection: Alert when traffic spikes unexpectedly
  • Custom date ranges: Not just last 30 days
  • Real-time dashboard: Live updates as clicks happen

Lessons Learned

1. Parallel Queries Are Essential

Sequential database queries turned our analytics slow. Parallel queries fixed it instantly.

2. User Agent Parsing Matters

Showing readable labels ("Chrome on macOS") instead of raw strings made the analytics 10x more useful.

3. Mobile-First Chart Design

Designing charts for mobile first, then scaling up, led to better layouts across all devices.

4. Index Your Analytics Table Early

We added database indexes after performance became an issue. Do it from day one.

5. Show Useful Defaults

Even when data is limited, showing all four metric cards and charts gives a complete picture.

Getting Started with Your Own Analytics

If you're building a URL shortener or similar service, here's the minimal checklist:

  • ✅ Capture user agent, referrer, and IP on each click
  • ✅ Create database indexes on frequently-queried columns
  • ✅ Use parallel queries to fetch analytics data
  • ✅ Parse user agent strings into readable format
  • ✅ Choose a charting library (we love Recharts)
  • ✅ Test mobile responsiveness early and often

Conclusion

Analytics transforms a URL shortener from "just a link tool" into a powerful marketing analytics platform. By combining smart database design, parallel query execution, intelligent user agent parsing, and beautiful visualizations, we created an analytics dashboard that users genuinely love to use.

The best part? The core logic is surprisingly simple—it's the details (mobile responsiveness, chart optimization, data parsing) that make it shine.

If you're using qz-l, head to the Analytics Dashboard and see what you discover about your traffic!


Want to dive deeper?

Happy analyzing! ๐Ÿ“Š

๐ŸŽ‰ QZ-L.com Now Serves 300+ Short Link Redirects!

Great news! QZ-L.com has officially passed 300 active short-link redirects — a big milestone for our lightweight and fast link-management s...