rogasper commited on
Commit
a5bd496
Β·
1 Parent(s): df94b74

feat: add sitemap.xml endpoint and improve SEO metadata across web application. Update landing page and root route with Open Graph and Twitter Card tags, enhance environment configuration for site URL, and introduce robots.txt for better search engine indexing. Include Umami analytics script for tracking user interactions.

Browse files
apps/server/src/index.ts CHANGED
@@ -56,6 +56,26 @@ app.use(
56
  }),
57
  );
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  app.get("/", (c) => {
60
  return c.text("OK");
61
  });
 
56
  }),
57
  );
58
 
59
+ app.get("/sitemap.xml", (c) => {
60
+ const baseUrl = "https://labas.rogasper.com";
61
+ const urls = [
62
+ { loc: `${baseUrl}/landing`, changefreq: "weekly", priority: "0.8" },
63
+ ];
64
+ const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
65
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
66
+ ${urls
67
+ .map(
68
+ (url) => ` <url>
69
+ <loc>${url.loc}</loc>
70
+ <changefreq>${url.changefreq}</changefreq>
71
+ <priority>${url.priority}</priority>
72
+ </url>`,
73
+ )
74
+ .join("\n")}
75
+ </urlset>`;
76
+ return c.text(sitemap, 200, { "Content-Type": "application/xml" });
77
+ });
78
+
79
  app.get("/", (c) => {
80
  return c.text("OK");
81
  });
apps/web/.env.example CHANGED
@@ -1,3 +1,3 @@
1
  VITE_SERVER_URL=http://localhost:3000
2
-
3
 
 
1
  VITE_SERVER_URL=http://localhost:3000
2
+ VITE_SITE_URL=http://localhost:5173
3
 
apps/web/index.html CHANGED
@@ -1,5 +1,5 @@
1
  <!doctype html>
2
- <html lang="en">
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -7,10 +7,14 @@
7
  <meta name="theme-color" content="#0c0c0c" />
8
  <meta property="og:title" content="Labas β€” AI Exam Practice" />
9
  <meta property="og:description" content="AI-powered multi-language test practice platform" />
 
 
10
  <meta property="og:type" content="website" />
11
  <meta name="twitter:card" content="summary_large_image" />
12
  <meta name="twitter:title" content="Labas β€” AI Exam Practice" />
13
  <meta name="twitter:description" content="AI-powered multi-language test practice platform" />
 
 
14
  <title>labas</title>
15
  </head>
16
 
 
1
  <!doctype html>
2
+ <html lang="id">
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 
7
  <meta name="theme-color" content="#0c0c0c" />
8
  <meta property="og:title" content="Labas β€” AI Exam Practice" />
9
  <meta property="og:description" content="AI-powered multi-language test practice platform" />
10
+ <meta property="og:url" content="https://labas.rogasper.com" />
11
+ <meta property="og:image" content="https://labas.rogasper.com/og_image.png" />
12
  <meta property="og:type" content="website" />
13
  <meta name="twitter:card" content="summary_large_image" />
14
  <meta name="twitter:title" content="Labas β€” AI Exam Practice" />
15
  <meta name="twitter:description" content="AI-powered multi-language test practice platform" />
16
+ <meta name="twitter:image" content="https://labas.rogasper.com/og_image.png" />
17
+ <link rel="canonical" href="https://labas.rogasper.com" />
18
  <title>labas</title>
19
  </head>
20
 
apps/web/public/og_image.png ADDED

Git LFS Details

  • SHA256: 59c312288ab9c3d5e5a2f0f301e40704816d1c139606ff8b0aa462de319aa9ea
  • Pointer size: 132 Bytes
  • Size of remote file: 1.75 MB
apps/web/public/robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ User-agent: *
2
+ Allow: /
3
+ Sitemap: https://labas.rogasper.com/sitemap.xml
apps/web/src/routes/__root.tsx CHANGED
@@ -32,18 +32,52 @@ export interface RouterAppContext {
32
  export const Route = createRootRouteWithContext<RouterAppContext>()({
33
  component: RootComponent,
34
  errorComponent: ({ error, reset }) => <ErrorFallback error={error} reset={reset} />,
35
- head: () => ({
36
- meta: [
37
- { title: "Labas β€” AI Exam Practice" },
38
- { name: "description", content: "AI-powered multi-language test practice platform" },
39
- ],
 
 
 
 
 
 
 
 
 
 
 
 
40
  links: [
41
- { rel: "icon", href: "/favicon.ico" },
42
- { rel: "preconnect", href: "https://fonts.googleapis.com" },
43
- { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" },
44
- { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Inter:wght@400;500;600&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&display=swap" },
45
- ],
46
- }),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  });
48
 
49
  function RootComponent() {
 
32
  export const Route = createRootRouteWithContext<RouterAppContext>()({
33
  component: RootComponent,
34
  errorComponent: ({ error, reset }) => <ErrorFallback error={error} reset={reset} />,
35
+ head: () => {
36
+ const siteUrl = "https://labas.rogasper.com";
37
+ return {
38
+ meta: [
39
+ { title: "Labas β€” AI Exam Practice" },
40
+ { name: "description", content: "AI-powered multi-language test practice platform" },
41
+ { property: "og:title", content: "Labas β€” AI Exam Practice" },
42
+ { property: "og:description", content: "AI-powered multi-language test practice platform" },
43
+ { property: "og:url", content: siteUrl },
44
+ { property: "og:image", content: `${siteUrl}/og_image.png` },
45
+ { property: "og:type", content: "website" },
46
+ { name: "twitter:card", content: "summary_large_image" },
47
+ { name: "twitter:title", content: "Labas β€” AI Exam Practice" },
48
+ { name: "twitter:description", content: "AI-powered multi-language test practice platform" },
49
+ { name: "twitter:image", content: `${siteUrl}/og_image.png` },
50
+ { name: "robots", content: "index, follow" },
51
+ ],
52
  links: [
53
+ { rel: "icon", href: "/labas_icon.png" },
54
+ { rel: "canonical", href: siteUrl },
55
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
56
+ { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" as const },
57
+ { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Inter:wght@400;500;600&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&display=swap" },
58
+ ],
59
+ scripts: [
60
+ ...(import.meta.env.PROD
61
+ ? [{ src: "https://umami-analytic.rogasper.com/script.js", defer: true, "data-website-id": "67a18412-12c8-44ef-9cd3-e04238d37e9a" }]
62
+ : []),
63
+ {
64
+ type: "application/ld+json",
65
+ children: JSON.stringify({
66
+ "@context": "https://schema.org",
67
+ "@type": "WebSite",
68
+ name: "Labas",
69
+ url: siteUrl,
70
+ description: "AI-powered multi-language test practice platform",
71
+ potentialAction: {
72
+ "@type": "SearchAction",
73
+ target: `${siteUrl}/bank?search={search_term_string}`,
74
+ "query-input": "required name=search_term_string",
75
+ },
76
+ }),
77
+ },
78
+ ],
79
+ };
80
+ },
81
  });
82
 
83
  function RootComponent() {
apps/web/src/routes/landing.tsx CHANGED
@@ -2,6 +2,18 @@ import { createFileRoute, redirect } from "@tanstack/react-router";
2
  import { authClient } from "@/lib/auth-client";
3
 
4
  export const Route = createFileRoute("/landing")({
 
 
 
 
 
 
 
 
 
 
 
 
5
  beforeLoad: async () => {
6
  try {
7
  const session = await authClient.getSession();
 
2
  import { authClient } from "@/lib/auth-client";
3
 
4
  export const Route = createFileRoute("/landing")({
5
+ head: () => ({
6
+ meta: [
7
+ { title: "Labas β€” AI-Powered Exam Practice Platform" },
8
+ { name: "description", content: "Generate AI-powered practice questions for JLPT, TOPIK, TOAFL, and more. Practice smarter with adaptive test preparation." },
9
+ { property: "og:title", content: "Labas β€” AI-Powered Exam Practice Platform" },
10
+ { property: "og:description", content: "Generate AI-powered practice questions for JLPT, TOPIK, TOAFL, and more. Practice smarter with adaptive test preparation." },
11
+ { property: "og:url", content: "https://labas.rogasper.com/landing" },
12
+ ],
13
+ links: [
14
+ { rel: "canonical", href: "https://labas.rogasper.com/landing" },
15
+ ],
16
+ }),
17
  beforeLoad: async () => {
18
  try {
19
  const session = await authClient.getSession();
apps/web/vite.config.ts CHANGED
@@ -24,6 +24,16 @@ export default defineConfig({
24
  },
25
  },
26
  plugins: [
 
 
 
 
 
 
 
 
 
 
27
  tailwindcss(),
28
  tanstackRouter({
29
  target: "react",
 
24
  },
25
  },
26
  plugins: [
27
+ {
28
+ name: "inject-umami",
29
+ transformIndexHtml(html) {
30
+ return html.replace(
31
+ "</head>",
32
+ '<script defer src="https://umami-analytic.rogasper.com/script.js" data-website-id="67a18412-12c8-44ef-9cd3-e04238d37e9a"></script></head>',
33
+ );
34
+ },
35
+ apply: "build",
36
+ },
37
  tailwindcss(),
38
  tanstackRouter({
39
  target: "react",
packages/env/src/web.ts CHANGED
@@ -5,6 +5,7 @@ export const env = createEnv({
5
  clientPrefix: "VITE_",
6
  client: {
7
  VITE_SERVER_URL: z.url(),
 
8
  },
9
  runtimeEnv: (import.meta as any).env,
10
  emptyStringAsUndefined: true,
 
5
  clientPrefix: "VITE_",
6
  client: {
7
  VITE_SERVER_URL: z.url(),
8
+ VITE_SITE_URL: z.url().default("http://localhost:5173"),
9
  },
10
  runtimeEnv: (import.meta as any).env,
11
  emptyStringAsUndefined: true,