Search Engine Optimization (SEO)

Principles

i18n.site adopts a non-refresh single-page architecture. To facilitate search indexing, it generates separate static pages and a sitemap.xml for crawlers to crawl.

When the User-Agent of the access request is identified as a search engine crawler, the request is redirected to the static page via a 302 status code.

On static pages, use the link tag to indicate the links to different language versions of the page, for example:

<link rel=alternate hreflang=zh href="https://i18n.site/zh/.htm">
<link rel=alternate hreflang=en href="https://i18n.site/en/.htm">

Local Nginx Configuration

Take the .i18n/htm/main.yml configuration file in the demo project as an example

host: i18n-demo.github.io
seo: true
out:
  - fs

pkg:
  i: i18n.site
  md: i18n.site

cdn:
  v:
  jsd:

Please first modify the value of host: above to your domain name, such as xxx.com.

Then, run i18n.site -n, and the static pages will be generated in the out/main/htm directory.

Of course, you can also enable other configuration files, such as first referring to the main configuration to create .i18n/htm/dist.package.json and .i18n/htm/dist.yml.

Then run i18n.site -n -c dist, and the static pages will be generated in out/dist/htm.

nginx can be configured as follows:

map $http_user_agent $botLang {
  "~*baidu|yisou|sogou|360|byte" "/zh";
  "~*facebookexternalhit|slurp|bot|spider|curl" "/en";
  default "";
}

server {
  http2 on;
  listen 443 quic ;
  listen 443 ssl ;
  listen [::]:443 quic ;
  listen [::]:443 ssl ;

  add_header Alt-Svc 'h3=":443";ma=99999;persist=1';

  server_name doc.flashduty.com;

  ssl_certificate /root/.acme.sh/doc.flashduty.com_ecc/fullchain.cer;
  ssl_certificate_key /root/.acme.sh/doc.flashduty.com_ecc/doc.flashduty.com.key;

  root /mnt/doc.flashduty.com;

# Do not cache server worker scripts for too long
  location = /S.js {
    add_header Cache-Control "max-age=600";
  }

# Set longer cache times for other static resources
  location ~* \.(js|css|htm|html|md|avif|json|ico|xml|rss|gz|mp4|png|svg|txt|webmanifest)$ {
    add_header Cache-Control "max-age=999999";
  }

# Set which static file the crawler uses as the homepage entry
  location = / {
    # If $botLang is not empty, it means crawler access and redirection according to the set language path
    if ($botLang) {
        return 301 $botLang/flashduty.htm;
    }
    add_header Cache-Control "max-age=600";
    rewrite ^ /index.html break;
  }

# Single-Page Application Configuration
  location / {
    if ($botLang) {
      return 302 $botLang$request_uri.htm;
    }
    add_header Cache-Control "max-age=600";
    rewrite ^ /index.html break;
  }
}

server {
  server_name doc.flashduty.com;
  listen      80;
  listen      [::]:80 ;
  location    / {
    rewrite ^(.+) https://$host$1 permanent;
  }
  location /.well-known/acme-challenge/ {
    root /mnt/doc.flashduty.com/;
  }
}

Configure Object Storage for Uploading Static Files

Static files can be generated locally, but a more common approach is to upload them to object storage.

Modify the out configuration above to:

out:
  - s3

Then, edit ~/.config/i18n.site.yml and add the following configuration:

site:
  i18n.site:
    s3:
      - endpoint: s3.eu-central-003.backblazeb2.com
        ak: # access key
        sk: # secret key
        bucket: # bucket name
        # region:

In the configuration, please change i18n.site to the value of host: in .i18n/htm/main.yml. Under s3, you can configure multiple object storages, and the region field is optional (many object storages do not require this field).

Then run i18n.site -n to republish the project.

If you have modified ~/.config/i18n.site.yml and want to re-upload, please use the following command in the project root directory to clear the upload cache:

rm -rf .i18n/data/seo .i18n/data/public

Cloudflare Configuration

Host the domain name to Cloudflare.

Transformation Rules

Add the transformation rules as shown below:

The rule code is as follows. Please modify the code "i18n.site" to your domain name:

(http.host in {"i18n.site"}) and not (
substring(http.request.uri.path,-3) in {".js" ".gz"} or
substring(http.request.uri.path,-4) in {".htm" ".rss" ".css" ".svg" ".ico" ".png" ".xml" ".txt"} or
substring(http.request.uri.path,-5) in {".html" ".avif" ".json"} or
ends_with(http.request.uri.path,".webmanifest")
)

Cache Rules

Add cache rules as follows:

(substring(http.request.uri.path,-4) in {".htm" ".rss"}) or ends_with(http.request.uri.path,"/sitemap.xml") or ends_with(http.request.uri.path,".xml.gz")

Redirection Rules

Set the redirection rules as follows. Please modify the code "i18n.site" to your domain name:

(http.host in {"i18n.site"}) and not (
substring(http.request.uri.path,-3) in {".js" ".gz"} or
substring(http.request.uri.path,-4) in {".htm" ".rss" ".css" ".svg" ".ico" ".png" ".xml" ".txt"} or
substring(http.request.uri.path,-5) in {".html" ".avif" ".json"} or
ends_with(http.request.uri.path,".webmanifest")
) and (
http.user_agent wildcard "*bot*" or
http.user_agent wildcard "*spider*" or
http.user_agent wildcard "*facebookexternalhit*" or
http.user_agent wildcard "*slurp*" or
http.user_agent wildcard "curl*" or
http.user_agent wildcard "*InspectionTool*"
)

For URL redirect, select dynamic redirection. Please modify /en in the redirection path concat("/en",http.request.uri.path,".htm") to the default language you want search engines to include.

Baidu Intelligent Cloud Configuration

If you need to provide services to mainland China, you can use Baidu Intelligent Cloud.

Data is uploaded to Baidu Object Storage and bound to Baidu Content Delivery Network.

Then create the script in EdgeJS Edge Service as follows:

const uri = r.uri, p = uri.lastIndexOf(".");
if (
  p < 0 ||
  !"|js|css|htm|html|md|avif|json|ico|xml|rss|gz|mp4|png|svg|txt|webmanifest|".includes(
    "|" + uri.slice(p + 1) + "|",
  )
) {
  const ua = r.headersIn["User-Agent"].toLowerCase()
  if (/facebookexternalhit|slurp|bot|spider|curl/.test(ua)) {
    r.return(
      302,
      (/baidu|yisou|sogou|360|byte/.test(ua) ? "/zh" : "/en") + r.uri + ".htm",
    )
  } else {
    r.uri = "/index.html"
  }
}

r.respHeader(() => {
  const t = [], out = r.headersOut;
  ["Content-MD5", "Age", "Expires", "Last-Modified"].forEach(
    i => delete out[i]
  )
  r.rawHeadersOut.forEach(i => {
    const key = i[0].toLowerCase()
    if (key.startsWith("x-") || key.startsWith("ohc-")) {
      delete out[key]
    }
  })
  out["Cache-Control"] = "max-age=" + 9e5
  // You can set the response header to debug the output, such as out.XXX = 'MSG';
})

Click Debug, then click Publish to the entire network.

Advanced Usage: Distribute Traffic Based on Regional Resolution

If you want to provide services in mainland China and also benefit from cloudflare's free international traffic, you can use DNS with regional resolution.

For example, Huawei Cloud DNS offers free regional resolution, which can be used to route mainland China traffic through Baidu Smart Cloud and international traffic through cloudflare.

There are several pitfalls in configuring cloudflare. Here are a few points to note:

If the domain is hosted on another DNS, how to use cloudflare

First, bind an arbitrary domain to cloudflare, then use SSL/TLS → custom domain to associate the main domain with this domain.

cloudflare R2 cannot be accessed through a custom domain

Since cloudflare's built-in object storage R2 cannot be accessed via a custom domain, you need to use a third-party object storage to host static files.

Here, we take backblaze.com as an example to demonstrate how to bind third-party object storage to cloudflare.

Create a bucket on backblaze.com, upload any file, click to browse the file, and obtain the domain name from the Friendly URL, which is f003.backblazeb2.com in this case.

In cloudflare, set the domain CNAME to f003.backblazeb2.com and enable the proxy.

Modify the SSL → encryption mode in cloudflare to Full

Add the transformation rule as shown below, placing it first (the first one has the lowest priority):

For Rewrite to, select dynamic and modify your_bucketname in concat("/file/your_bucketname",http.request.uri.path) to your bucket name.

Additionally, in the cloudflare transformation rule mentioned above, change index.html to file/your_bucketname/index.html, and keep other configurations the same.